2

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?

3 Answers 3

3

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.

7
  • 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. Aug 6, 2020 at 18:44
  • 1
    @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, 2020 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. Aug 7, 2020 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, 2020 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, 2020 at 9:29
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.

1
  • Encapsulating state is pretty much the entire point of JWT: unlike a traditional opaque token, it allows the receiving application to be completely decoupled from the store of authorisation information (e.g. a database containing user permissions). Most of what you've written here is about general disadvantages of JWT which have been discussed endlessly elsewhere, rather than answering the specific implementation question.
    – IMSoP
    Nov 30, 2020 at 10:14
0

Thanks for suggestions. I ended up removing frontend related permissions from token, keeping only backend permissions, and it's working fine. Later I'll probably add friendly names and use abbreviations, as @Dom suggested.

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.