3
votes

Background

I am working on a web application using Spring Security and am trying to use JSON Web Tokens for authentication for the first time. The app should restrict access to certain URIs based on user roles. It should provide password change option and enable Admin users to change other users' roles.

In a tutorial I've followed, on each HTTP request to the server the database is hit by CustomUserDetailsService to load the current details of the user which seems heavy on performance:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    //...

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                Long userId = tokenProvider.getUserIdFromJWT(jwt);

                UserDetails userDetails = customUserDetailsService.loadUserById(userId);
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    //...

}

The author of the tutorial suggests another option:

Note that you could also encode the user's username and role inside JWT claims and create the UserDetails object by parsing those claims from the JWT.

However, this comes at the cost of making it difficult to change user's role as we have no way of discarding issued tokens, without keeping track of them.

Possible solution

I've researched the topic of JWT and came up with the following solution:

Let's store username and role inside JWT claims and set a short token expiration time (using exp claim) - after this period, e.g. 15 minutes, we hit the database to check user's details. If the role has changed, we generate the new token with the new role in payload. If the password has been changed, we require the user to re-authenticate, before generating the new token.

An obvious downside of this solution is that any change in user access rights is effective after the period of expiration time.

Question

Are there any other ways of tackling the issue of handling user details change while using JWTs?

1

1 Answers

2
votes

We use JWT tokens with Spring Security and an Angular webapp.

I think your Possible Solution idea is valid. We handle this in a similar manner. Our auth flow looks like:

  • User signs in at a URL and the response header contains the JWT token
  • JWT tokens have a short timeout (minutes)
  • The webapp pings a 'refresh token' service at a shorter interval which detects if a token is valid. If so, the server re-issues a new token including any updated roles for the user, and this is then stored by the webapp for inclusion in future requests to the backend.

Due to the 'refresh' service, if a user's roles change, or if they're banned from the system, they will be automatically notice the new role or be 'locked out' no later than the token expiration time.

This has worked well for us for years now. Our users' roles don't frequently change, and if it's ever desired to have their role immediately updated, they can always sign out / sign back in.

Additional potential solution

However, if it's paramount to have the user's role immediately updated in your system, you could have a Filter in Spring check for the JWT header on each request, and have it do the JWT verification, and add a new, refreshed JWT token on every response.

Then your client can expect to get a revised JWT token on each response back from the server, and use that revised JWT token for each subsequent request.

This would work, but it'd also be relatively expensive if you have a lot of traffic.

It all depends on your use case. Like I said, the 'refresh' service has worked well for us.