loading...

How do you handle role/permissions updates with JWT?

sebastiandg7 profile image Sebastián Duque G Updated on ・1 min read

Usually, JWT implementations in a REST Api backend save the user roles and/or permissions inside the JWT token claims. In this cases, clients make use of this claims to restrict user's interaction with some of the app features.

There are some user, or server, actions that update the user's authorization roles/permissions. However, the last emitted JWT token has not expired yet so it still has the old roles/permissions in it's claims.

How do you or your team handle this scenarios in order to update user's authorization in client side apps?

I've seen many options out there:

  • Create a token version and update it in server side to compare with new request
  • Keep token lifetime short
  • Use refresh token mechanism and invalidate the user's current token

I want to hear you...

Posted on by:

sebastiandg7 profile

Sebastián Duque G

@sebastiandg7

Software Engineer 🖥️ @trafilea | Focusing on building scalable & maintainable future-proof applications

Discussion

markdown guide
 

Conflating authorization (roles, permissions, etc) and identity (name, email, userId, etc) into the same token can lead to a bunch of problems.

Roles and permissions change much more quickly than a name does, and you may need to revoke a role or permission on short notice. Think of a JWT like a driver's license. It's a stateless "token" issued by a central authority (the DMV) which makes some claims about you (name, DoB, license #) which can be verified by the security features on the card (I.E. looking at it under a blacklight). If you think of what information goes on something like a drivers license, it's just the identity information. If you get pulled over, the cop will use that token to establish who you are, but will have to run the license to see if it was suspended to see if you're actually allowed to be driving.

Although keeping roles and permissions in tokens is somewhat commonplace, it definitely has some drawbacks. Check out this video for some more info on it: vimeo.com/254635640. I think it's a much better setup to have the token establish an identity (authentication), but checking whether a user is allowed to perform a specific action (authorization) should require a check with some kind of central authority to see what the user is allowed to do.

 

Agree. This also helps the JWT to be as slim as possible.

I tend to only want the key that can help me get to information about a resource (usually some type of ID).

"Normal" methods of dealing with performance issues should be applied at this point if having to fetch authorization details is causing issues.

 

Thanks a lot for sharing!

I tend to handle it this way. The user's authorization data is queried with a REST endpoint (/api/users/me). The main challenge with this approach is knowing what kind of user interaction triggers an authorization data update in order to have new changes as "real time" as possible. Usually, this interaction is related to users navigation in the app menu.

EDIT:

Your driving license analogy is great!

 

This was an excellent explanation... I wish I could bookmark comments.

 

If you need to know whether the user's permission changed before processing every request, then you will have to look up the information on every request, which is going to increase your latency. There's no way around it. To mitigate the increased latency, you can use something like Redis (memory-based DB cluster) to store the current user permissions so that lookups are fast. (You could also try more complicated solutions like a pub/sub notification on user changes. But this seems like really a high burden of proof of need vs complexity.)

Having the permission changes immediately reflected always costs something. Even before JWT existed, this was a known issue, so security frameworks would cache the permissions for a period of time. This led to the same issue of the user continuing to have the old permissions until the cache expired. You can look at the JWT as a cache.

The larger question is: how important is it that permission changes are immediate? For us, it is not that important. For scenarios like deactivating a user or changing user permissions, we inform the users that it may take up to an hour to take effect (which is the token/cache lifetime). Under extreme situations, like an active attack, there are nuclear options like changing signing key (so all of the existing tokens are invalidated) or shutting down the attacked service.

Lastly, I want to say that for our latest app, we have gone to a model where we no longer keep permission claims in the JWT. We keep the permission in our own database and after we read them for a particular user we cache it for a (configurable) period of time to amortize the latency cost. We do this for a couple of reasons. One is that the JWT size kept increasing as our app features increased. And it had to be sent on every request. Two is that it was awkward to manage user permissions through our auth provider, especially if we want to expose them to users to manage themselves. Ultimately we went for a scenario where the auth provider knows almost nothing about the user. It only handles the hard (or uninteresting) problems: auth, password storage, token issuance, forgot password, etc. When we create the user in the auth provider, we attach their ID to our own user. So when the token comes in, we can lookup our user permissions from their ID.

 

I really like the idea of using the token just for user authentication.

Most of the times client apps UI change drastically according to user's permissions. The main reason of having the authorization updates as quick as possible in client side is because of UI/UX concerns.

 

If it is part of the core business that your app is meant to solve, then I understand. (Otherwise, I wouldn't invest too much in solving it until it is proven to be a big enough support burden.) To solve it, seems like you would need to setup a pub/sub system to be notified of changes soon after they happen. From the browser maybe web sockets with long-poll fallbacks. There are numerous pub/sub options for the backend, depending on your needs.

I get it. But maybe a pub/sub strategy is too much. You could define some specific (not so regular) user events in your app that triggers the authorization data update, like going from a big module to another.

Additionally, reacting to 401 Unauthorized responses could help to deal with this (401 > fetch authorization data).

Additionally, reacting to 401 Unauthorized responses could help to deal with this (401 > fetch authorization data).

For sure. But the back-end will still need a way to be aware of permission changes or else recheck the permission store every request. (If you are still expecting changes to be immediate.)

That’s assuming we are targeting scalable workloads. If the service doesn’t need to scale and it also responsible for making the permission changes, then you might just be able to keep permissions loaded in memory, and update them as changes happen.

 

I would separate permissions and authorization.
Have an endpoint fetch the permissions of a user authorized by a token.

This gives you better flow control and allows mechanisms like caching, real time permissions updates, etc.

 

this is actually good, differentiation on each job, make the API clean. thank you. i wish i could like this comment more than one

 

I agree, there are trade offs to both sides and we've been contemplating our strategy with a new site we're building. Having instant reaction when permissions have changed is attractive but querying the user's authority to do something every request is definitely a huge overhead with very little benefit, since 99.9% of the time your data is the same as the last query.

Storing this info in the JWT is also an idea but as many have pointed out starts to really cause issues with scale, and it just feels wrong.

We're using Firebase with an Angular front end for our upcoming project. One approach we are considering is to store the roles and resulting permissions in a database document representing the user's profile. When the user logs in we will read that with an Observable. All routing and UI will be based on the access granted to the user. Because it's an Observable we never have to requery it on a regular basis. If the data were to change the Observable would instantly be notified and obtain the latest revision, cascading through the UI.

Since the back end never trusts the data from the client we have to verify the user actions anyway. Data access will be handled with Firestore rules which verify authentication and match against the permissions stored in the user's profile/permissions document. Any Function API would do the same. Functions are stateless so there's no ability to cache and we are forced to check per request anyway.

We haven't built this out fully in production but that's our initial architecture. Should see how it works out in the next week or so.

 

I'd love to know how this works by when you have it ready. Some time ago I did an Angular + Firebase app with a similar structure. But, we store user roles inside the Firebase generated token using auth sdk in functions, there was a db node (realtime database, not firestore) which the client app was listening for changes in order to know when to fetch a new token and update it's roles.

That way, db access rules could be written using the 'auth' object... But things didn't end up very good as we expected it. The function in charge of assigning the recently created user sometimes took very long to do it's work (related to the function's cold start).

Any way, your architecture seems to be promising. Let me know how it ends!

 

If your scenario requires authorization lookups for every call (ie you cannot tolerate the staleness of a jwt, no matter how short) then token based auth is probably not the right solution.

Using token based auth adds complexity, if you are still going to make authorization calls every time you are paying that cost for no gain.

 

Just keep the roles in claims and not permissions so that JWT size doesn't creates a problem. Now, have a field in database like isClaimsNeedToReset. Make this field true whenever a property stored in claims is changed. Now, on each request check this property if it is true then logout a user or silently refresh user JWT. This way you can reset user claims immediately and ensure that user doesnt uses outdated claims. This behavior can also be used when a new property is added in claims during a new release.