Skip to main content

JWT access token misconceptions

· 5 min read
Warren Parad

Identity providers solve the issue of identity verification, but never include solutions for CIAM access management. These CIAM providers handle authorization separate from the authentication provider. This separation creates opportunities for confusion, so we'll discuss some of the common misconceptions surrounding JWT authorization below.

JWT access tokens containing roles solves authorization

  • A user should have access to read all resources in a tenant
  • Another user should have access to update all resources in the tenant

These are common user stories, and often can be solved by adding a single string identifier to the JWT access token containing the relevant Tenant ID and the Role (User or Admin in this situation). This can definitely work, although it doesn't scale. If an application has one api, and the number of users that share a tenant is small (for example less than 5), then each user may have a different role and everything works. However as soon as there are more users, more services, or more resources, it starts to break down. Very soon every user will need to have a different role to be able to correctly identify the right permissions.

Further, a role's meaning will be obfuscated as one service understands the roles Admin and User but another also has Editor. Which permissions should the User get and to which service. Users and services trying to understand access get frustrated or worse incorrect grant access to secured resources.

It's easy to add resource access to a JWT

One way to solve role confusion is to include resources also in the JWT, User X has Y role for Z resource. But this doesn't scale. JWTs are limited in size, and more than a couple of resources will break the JWT usage.

Another critical problem are permission changes. Often, when a user should receive access to a resource, they need it immediately, it can't wait. JWTs expire on the order of hours. This means new permissions, if stored in the token, and removed permissions won't take effect until after the user gets a new token. No identity provider has a way to expire tokens, this isn't how JWTs work. Having a CIAM authorization service separates the roles the user has from the token, allowing both to freely change as necessary.

Making calls to an authorization service is too expensive

Having the roles in the token is easy, it's quick, and it's cheap from an external service standpoint. But only short term. It actually isn't expensive to create a service to handle all these requests, but it expensive to manage it. Using a CIAM authorization scales as your usage does. Additionally it separates the concerns about the user identify from the access control. CIAM services are fast because they perform the access checks directly knowing about resources, whereas identity providers have no knowledge of how resources are organized.

While running an AuthZ CIAM service is subject to the difficulty of successfully building an authorization service (Think Build), it might be considered expensive to reach out to an external service at runtime (Think Buy). Most present day architectures already separate services, external authorization services fit perfectly into existing tech stacks and support migrations to other architectures such as microservices. With out of the box caching and opportunities for client side optimizations authorization calls are fast and reliable.

Migrating authorization to an external service is costly

The critical point for migration to an authorization service is at the time of rearchitecture. Legacy services still need to be supported but new service paradigms are being created. Existing services and new services need to be able to successfully depend on always available apis and easy to integrate with access control interfaces. The migration is the easy part however, since your application has already done the difficult part of deciding what features are necessary and modeling the services and their permissions and roles, this directly translates to to Authress' resources, roles, permissions, and access records without additional work. Authress also provides migration wrappers and concierge migration support to ease transitions to or from any other system, because it focuses on abstracting the complexity away. For example, with using AWS, Authress provides an EventBridge integration to enable crossing the cloud gap.

Identity Providers handle authorization because they create authorization tokens

Probably the most confusing concept is what identity providers actually do. Identity providers often suggest they create authorization tokens. These tokens are JWTs, which should be used as Bearer Tokens in the Authorization header of an HTTP request. This is true, what isn't true is that there is any information about what access this token provides. A token represents an identity, the data in the token about the identity is very slow changing, and that's it. These tokens are verifiable via public key certificates.

So, where's the authorization part? The tokens are used as the identity for services to authorize users. That is, the Authorization token is an identity, used to authorize the user. They say This is the token to be used for authorization.

Aren't scopes usable for access control?

No. Scopes represent the permissions an identity gives to the holder of the JWT access token, and Not the permissions the user actually has. A scope can say: profile--meaning that the holder of the token can have access to the user's profile. But a scope can't say access to resource X, because scopes are directly controlled by the user, the user could elevate access by telling the identity provider that I approve access to resource X without every having it. If you use scopes for permissions then you are let your users control what they have access to themselves. An example of this attack against vulnerable services, can be seen using AWS Cognito.

The token generator has no idea if the user has access to resource X to be able to delegate it to the holder of the token. If it did that, then you would be leaking access to resources, since scope properties are directly controlled by the user. Scopes must always represent resources that a user has access to a priori and not ones that are configurable for access via an authorization rules such as ACL, RBAC, ABAC, or Resource-Based.

Authorization checks are always necessary

Not every resource in your api needs to check for permissions. Only shared data, data shared between users, but is not public needs to have checks. Even in those circumstances, users with access to only one tenant can depend on less complex checks. In cases such as social media where everything is public, or a game and streaming service where everything is private, there is no reason to have authorization checks.

The total cost of ownership (TCO) of permissions management is low

The initial set of features for a CIAM service is small. It is only used by a couple of services, with shared context about the expectations. But this doesn't last long. The average time for a CIAM system to become legacy tech debt is 6 months. At the sixth month mark CIAM systems become a source of pain for users, time sinks for development teams, and fountain of vulnerabilities as they lack the attention and dedicated resources needed to maintain them. This is a large topic, so there is a dedicated article on building your own authorization service.

CIAM is a feature of an identity provider

Most identity providers support configuring some sort of roles, unless you are using a dedicated provider or a social login provider. But these barely solve even 1% of access control needs. That means using an identity provider and a CIAM provider. These don't have to be the same, and frequently they aren't. Depending on the needs of the product or application, it makes sense to fully evaluate the existing identity providers to pick the right one for your requirements.

The roles users have will never change

When roles are defined in the JWT access token, they can't be allowed to change. That's because tokens are immutable and frequently there is no way to get another one until the current one expires even if the user logs out and back in. And in technology everything changes. So if these roles are not static, if they are user editable then putting roles in the token will cause issues for your application or platform. For more information on issues with RBAC in general, check out the security article on Choosing an access control strategy.