1

I have .NET Core WebApi application with JWT token for authorization. I also have roles and permissions.

To make it more flexible for end user, I decided to make permissions like this:

[Authorize(Policy = "HasAccessToThisMethod")]
[Route("someMethod/{id}")]
[ProducesResponseType(typeof(int), 200)]
[HttpPost]
public async Task<IActionResult> SomeMethod(int id)

And store them in the tokens. With this approach, user can create whatever roles he/she wants and add permissions to them.

Recently, I had a problem with token size, it exceeded the default 16kb Windows/IIS limit. I increased it and called it a day, but after reading more about tokens I understood, that they usually are ~4kb.

Questions:

  • How bad is this approach?
  • Is it okay that token grows with permissions count?
  • Is replacing [Authorize] with custom middleware would be better?
    • It would check the user's permissions according to his uid/role/etc and not keeping all permissions in token.
    • If not, what solution is better?
1

You're effectively distributing state within the tokens which are typically handled by other means (like storing it in the backend). What you're doing can work, but there's a at least a few questions I might ask weighed against alternatives:

  • What is the cost of increasing the token size? This token is presumably sent each request increasing bandwidth and needs to be parsed and validated increasing server load. On systems with limiting resources (mobile, servers processing heavy requests, servers that have cost directly associated with cpu time) this could end up being a high priority to undo for optimization.

  • Who is signing these tokens, how often do you rotate signing keys, and what is the lifetime of the tokens, what is the refresh and revocation strategy? Allowing state which defines roles and permissions to be held by the user, you increase security risk if it's not properly signed or when your keys are compromised.

| improve this answer | |
1

It sounds like you have a lot of permissions, and maybe those permissions have very long names. One way of reducing the overall token size would be to severely abbreviate them, and add a static class of constants to make them humanly readable.

eg.

public static class Permissions
{
    public const string HasAccessToThisMethod = "HATTM";
}

and

[Authorize(Policy = Permissions.HasAccessToThisMethod)]

Of course this method won't work with infinite permission growth, or if the list of permissions is already ridiculously long, but it's a low effort, high impact change in the short term.

Is this approach with JWT tokens wrong?

I've seen various different approaches in use, and companies/developers seems to craft them based on needs and/or knowledge level.

I've implemented exactly this approach (of storing permissions in the token - not abbreviating them) in a previous role, and it's still working fine to this day. The big advantage of it is avoiding a round trip to the database for every single authorised request, which is always a plus but not always the most important thing, and should be sacrificed if API speed is not a number one priority and it is causing other issues.

| improve this answer | |
  • Even if API speed is a priority, you don't need to encode the permissions in a token. You can avoid a lot of the DB requests with caching, especially for a single user making many requests in a short time. – Mad Scientist Aug 6 at 18:44
  • @MadScientist caching is significantly more complicated. It's not wrong but it's usually a much bigger investment. In the age of horizontal scaling it's especially complicated and expensive. If you're developing an application that doesn't need too many permissions, and one that can allow up to say, 5 minutes for access to be revoked, putting the permissions into the token is a no-brainer. – Dom Aug 7 at 7:57
  • Above a certain scale JWTs have a real advantage. But I don't think caching is inherently more complex in this case because JWTs share the biggest disadvantage of a naive cache. You can't easily invalidate them, and the permissions in them are not necessarily consistent with the permissions in the DB. I'd probably agree that permissions in JWT are good for higher scale, but not necessarily for higher performance. – Mad Scientist Aug 7 at 8:26
  • @MadScientist I think maybe we're envisioning different implementations of JWT. My preferred implementation includes a refresh token that is used to get a new bearer token when the next request fails from the client, as the bearer token is only valid for x (5 is reasonable imo) minutes. When the new token is being created, the round trip is made to get updated permissions or revoke access. For many applications, a 5 minute delay for changes is an acceptable compromise versus the significant additional work of implementing robust, scalable caching. It all depends on requirements of course. – Dom Aug 7 at 9:24
  • I should add that implementing JWT authentication in .NET Core is very straightforward due to the native support. For other frameworks and languages the effort required may be more similar to the effort of implementing caching. – Dom Aug 7 at 9:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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