Question

Understanding JWT Validation in Spring Boot with Keycloak

I recently encountered an issue while implementing an application using Spring Boot and Keycloak, and I'd like to share and discuss it here for clarity.

Scenario: I have a user with an admin role in Keycloak. Initially, I logged in this user using the endpoint localhost:9091/realms/test-realm/protocol/openid-connect/token. Subsequently, I attempted to access an endpoint in my controller using the JWT token obtained from the login endpoint. This endpoint requires the user to have the admin role for authorization, and everything worked as expected.

Unexpected Behavior: Here's where things got confusing. After successfully accessing the endpoint, I went to the Keycloak console and revoked the admin role for that user. However, when I sent the old JWT token (which still had the admin role embedded in it) to the same endpoint again, to my surprise, the user still had access.

Concerns: This behavior raised concerns about the security implications. Ideally, changes in user roles (like revoking a role) should immediately invalidate existing JWT tokens to prevent unauthorized access.

 2  73  2
1 Jan 1970

Solution

 2

A JWT access token cannot be revoked. When resource servers use JWT decoders, if JWTs are sent to a frontend running on a remote device, all that can be done is wait for it to expire.

There are other reasons why sending access tokens to single-page or mobile apps is now considered a bad practice:

  • using confidential clients is much safer than public ones
  • the tokens are pretty vulnerable in Javascript code and device storage

For this reason plus the one you face (keeping control over user session), tokens should not leave your backend.

The solution is using an OAuth2 BFF. I wrote an article for that on Baeldung.

2024-07-13
ch4mp

Solution

 0

You have the same problem as here, I guess. You need to do extra check every time if any changes arise and manually reject the request. you can always save those changes in your DB, like token_id and status_invalid, so its easier to check.

You can use SSE or some other methods to keep client up to date if any changes, and invalidate their session immediately.

2024-07-11
A. O.

Solution

 0

There are three things to keep in mind:

  1. What your spring-boot application did, was a "local validation" of the JWT. It checks (based on your implementation and configuration) certain JWT claims e.g., iat, nbf, exp, roles, the token signature and so on. This will not fix your concern.
  2. What you can also do is, ask the OpenID provider (keycloak) about the token ("remote validation") which is called introspection of an opaque token. Spring-boot-security offers it out of the box, you just have to configured it. What it does, it sends the JWT to KC and KC checks whether the token is still valid i.e., "active". It does so by checking if the user still exists, or if the user-session still exists (depends on certain JWT claims), or if the JWT has been revoked via OIDC means. This measure itself will not fix your concern.
  3. But what you cannot do out-of-the box, is make your application aware that the user does not have a certain role (i.e., admin) anymore. At the time the JWT was issued, the user had all necessary roles for accessing a resource in your application. This might fix your concern, as the token gets invalidated at the OpenID Provider, and when performing introspection the token will come back as not valid. The only issue is, that you have to revoke the token, or kill the user session.

A solution: What you can do is, make the JWT token's lifetime shorter. This way the token gets rejected after e.g, 10 minutes of use, and the user has to re-authenticate. Reauthentication triggers the generation of a new JWT, this time WITHOUT the revoked role. If the user then tries to access the same resource again, the application will deny it due to the missing role.

2024-07-11
Manuel

Solution

 0

The login endpoint uses the roles in the JWT to determine access. Once a JWT is created with a role the user has that role until the token expires. Removing the role from the user does not affect the token in the wild.

This is expected behaviour.

Because the JWT contains everything needed for authz it allows stateless sessions and therefore easy horisontal scalability and easy microservice architecture.

The alternative is for each business system to hit the db with each call to re-check the roles, like we did back in the day.

There are other ways, eg maintaining lists of ‘revoked’ tokens and having the business services always check these. Lots of engineering for a small problem.

If the security requirements genuinely require instant loss of a role then JWT is probably not the right solution.

2024-07-11
John Williams