0

We have an ongoing argument in our team. Please help. Here is the problem: In our SPA web app, let's say we have a resource which can be edited by only those users who belong to the team of the user who created that resource. Now, we are sending this resource to the multiple places in the frontend. At some of those places this resource can be edited in the frontend by the logged in user. What is the best way to handle authorization in the frontend? By 'handling authorization' I only mean controlling the UI (whether to show edit button or not). I know that the real authorization security checks must be handled in the backend in any case. After discussion we have come down to the following two options:

  1. Should we send the authorization info inside the resource to the frontend, i.e. whether the logged in user can edit the resource or not? e.g. the Resource is a Todo item, then it will look like this:
    {
        'id': 1,
        'subject': 'Determine best approach for handling authorization in the frontend',
        'creator_id': 1000,
        'last_editor_id': 1001,
        'team_id': 20,
        'can_edit': true
    }
  1. Instead of sending authorization info in can_edit, let the frontend determine the same. For this, the frontend must re-implement the logic for whether this resource is editable or not by the current logged in user.

I don't like the first approach because we are tightly coupling the resource with the auth. Also, I feel we are coupling backend with frontend. If backend forgets to send this extra piece of auth info inside the resource, the frontend won't know whether logged in user can edit this resource or not.

The second approach is also not ideal because we will be re-implementing the same logic for authorization in the frontend as well as backend.

Which one is better approach? Is there a third better approach? What is the best practice?

Extra note: This question lies at the edges of UI/UX, Security and Engineering Best Practices. I decided to put it here because I do not have any doubt about security. I know that it must be handled in the backend in any case. Also, because our doubts include the option 1 above I felt engineering stack exchange to be the best platform to get the answer.

3
  • 1
    Unless your web app needs an "offline" mode (without an internet connection), then there should be no problem having the app tightly coupled to the backend; indeed most most users these days tend to be fine with this. Building an app to have sufficient functionality in 'offline' mode tends to be a much greater investment in time/effort, particularly with regard to testing and maintenance so I would start there and decide whether offline functionality is a current requirement worth the extra effort, otherwise 'coupling' a front-end and backend together is generally a non-issue. Jan 7, 2023 at 11:33
  • Thank you for the comment. Offline functionality is not required. I have two frontends for the same backend, one written in JS (vuejs) for webapp and another in Dart (Fluttter) for mobile app. Especially in dart I have a model for each backend resource. So a Model each for User, Team, TodoItem etc. When I receive data in response, I quickly store them in respective models. In the first approach of combining authz info in resource data, how should I model it in the feel. It doesn't feel great practice to me to add a canEdit boolean field in the ToDoItem Model. Jan 8, 2023 at 12:25
  • Are there any specific problems or difficulties which would be caused by including the field in a model? Could they be solved by separating API request models away from the API response models? or indeed separate models depending on the particular action and type of request? (for example, a UI might typically define entirely different UI/view/pages and actions each for Create/Read/Update, suggesting that these would likely also involve using different models - perhaps consider separation such asCreateUserModel, UpdateUserModel UserDetailsModel etc.) Jan 8, 2023 at 13:39

2 Answers 2

1

The first approach is better a it actively avoids inconsistencies (bugs) around what can and cannot be done by a given user. The objection is more of an idealism than a true problem.

I honestly don't see the problem with the logic to determine editability being part of the call. That back-end is where you can (and ultimately must) determine authorisation along with anything else you need.

0

As you observe, authorizations and denials will come exclusively from the BE. There is a race between UI click actions and other activities like "disable this user" or similar ACL changes. We always need to send proposed change to BE and then see if it was accepted.

So the FE wants some hints, to let it grey out certain options. One way to implement that is by sending hint results with each BE update.

An arguably better way is to decouple them. The FE will ultimately send certain updates which return 200 or failure, and we can't predict the final status, we can only make a guess. Augment the API so there is a dryRun bit; a call will have no effect if the bit is set. When displaying the UI, choose initial defaults for whether a verb should be greyed out or not. Immediately launch a few dryRun requests, and use the results to update relevant menus.

The BE and FE codebases likely have different maintainers, and there is danger of drift between them. Making BE requests of same version of same endpoint, with flag set or cleared, should mitigate several maintenance concerns.

EDIT

I strongly favor approach (1.) over any approach that involves lots of "re-implementing" such as (2.).

A todo item might have associated update() & delete() functions, and /update & /delete endpoints. Typically we will produce clean implementations of all those, and then examine observed FE query patterns and offer a "jumbo" endpoint that reduces roundtrips by piggybacking related requests on the primary one. If BE knows that FE always asks about A,B,C in rapid fire, then it makes sense to volunteer the B,C info when sending an A response, if that's cheap to do.

I was proposing that the manner of synthesizing a can_edit boolean could be to ask about update(id, creds, dry_run=True) and see if it produced a permission error. The goal is to keep all the auth decisions bundled within the same function, to ease the maintenance burden.


In a product of any interesting size, we are likely to see different languages for front- and back-ends, and different specialists maintaining code in their preferred language. So the architecture task to support their coordination.


Fielding's HATEOAS suggests an alternative to sending a boolean.

With a todo in hand, you can potentially update or delete it. Include those endpoint URLs in each response that describes a todo. See the github or stackoverflow APIs for examples of this approach.

FE developers will notice the pattern and be tempted to synthesize URLs on their own. Don't let them. Version the base endpoint URL, so you can release /v1.2 followed by /v1.3. Additionally include a hash in derived endpoints such as for update & delete. Ensure that the hash changes at least with each release. It could be as simple as a prefix of sha3(secret, "/v1.3"). This way a FE developer who wasn't adhering to the contract will soon see the error of his ways, and fix his code to only use URLs supplied by the BE. This makes BE code maintenance the focus when there's refactors and new features.

2
  • > The BE and FE codebases likely have different maintainers This is possible, but I think a better way of organising it is to have a single team providing full-stack functionality. That means there's some chance of the team choosing it's own priorities. It's no good if FE and BE teams prioritise different two features and neither are completed end to end.
    – bdsl
    Jan 8, 2023 at 11:36
  • Thank you for your answer. I am not sure what you mean by "One way to implement that is by sending hint results with each BE update." The other decoupling approach you mentioned is also interesting but I worry that the UX might not be ideal. A button might be disabled for a moment until this request is complete. What do you think about the two approaches I mentioned? The authZ logic in our case is not too complex so as to it can't be also implemented in the frontend, but it is also not very simple that doing it in the frontend (for controlling the UI) is the obvious choice. Jan 8, 2023 at 12:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.