How to implement JWT authentication in Spring Security and Angular – Part 5

In this last part of the series, I’ll show you how to implement a token renewal mechanism. This is necessary because the tokens issued by the backend expire after a certain amount of time. This means, that logged-in users can not authenticate themselves once the JWTs expire. To prevent this from happening, the frontend will automatically request a new token from the server shortly before the old one expires.

Alternatively, you can also automatically log out users if they haven’t interacted with the page for a given time. However, I won’t cover that.

Creating a second token

In part three of the series, I implemented the createToken(UserDetails, Boolean)-method in the JWT provider class. The second parameter is a boolean called futureToken. So far, it didn’t serve any purpose in the method.

But now, we’ll use it to create a second token whenever a user requests a token. This future token, as we’ll call it, is then used by the frontend when the originally requested token is about to expire or if it has expired a short time ago. This way, the backend can renew a logged-in user’s token:

public String createToken(User userDetails, boolean futureToken)
{
    Date now = new Date();
    Date validity = futureToken ? new Date(now.getTime() + SecurityGlobals.TIME_TO_LIVE * 2) : new Date(now.getTime() + SecurityGlobals.TIME_TO_LIVE);
    Date validFrom = futureToken ? new Date(now.getTime() + SecurityGlobals.TIME_TO_LIVE / 2) : now;

    Claims claims = Jwts.claims()
                        .setSubject(userDetails.getUsername())
                        .setIssuer(SecurityGlobals.ISSUER)
                        .setIssuedAt(now)
                        .setNotBefore(validFrom)
                        .setExpiration(validity);

    claims.put("administrator", userDetails.getAdmin());
    claims.put("id", userDetails.getId());

    return Jwts.builder()
        .setClaims(claims)
        .signWith(SecurityGlobals.SIGNING_KEY)
        .compact();
}

Compared to the version from before, the only difference is that the backend performs a check that validates whether the futureToken variable is true. If it’s false, the validity and validFrom variables are generated as before. However, if it’s true, the server generates a second token (the futureToken). It is valid for double the time from now and valid from the time when the original token expires.

Changing the AuthenticationResponse

Now, to send both tokens to a user who requests a token, we’ll have to also update the AuthenticationResponse class. Add another String variable together with its getters and setters:

public class AuthenticationResponse
{
    private String token, futureToken;

    // Getters, Setters
}

The authentication request remains unchanged.

Updating the authentication endpoint

By now, we have the tools to create a future token. However, the server doesn’t generate it yet. Furthermore, the frontend has no way to renew a user’s token using the future token.

Add the following line to the AuthenticationEndpoint’s createBearerToken method, to create a future token whenever a token is requested:

// ... Other code

else
{
    response.setToken(authenticationTokenProvider.createToken(user, false));
    response.setFutureToken(authenticationTokenProvider.createToken(user, true));
}

// ... Other code

By setting the second parameter of the createToken method call to true, the backend application generates the future token. Both tokens are returned to the client who requested them.

To allow the frontend to request a new token for an already logged in user (token renewal), we’ll add a new GET mapping to the authentication endpoint:

@RequestMapping(value = "/", method = RequestMethod.GET)
@CrossOrigin
public AuthenticationResponse renewBearerToken(@RequestHeader(SecurityGlobals.HEADER_FIELD_NAME) String authorization)
{
    String token = authenticationTokenProvider.getTokenFromHTTPHeader(authorization);
    String username = authenticationTokenProvider.getUsername(token);

    AuthenticationResponse response = new AuthenticationResponse();

    try
    {
        User user = users.findOneByUsername(username);

        response.setToken(authenticationTokenProvider.createToken(user, false));
        response.setFutureToken(authenticationTokenProvider.createToken(user, true));
    }
    catch (Exception e)
    {
        // Proper exception handling needed
    }

    return response;
}

As you can see, the backend doesn’t perform a new login. Instead, it reads the username from the supplied token. Note that the token renewal, unlike the initial request, will require the authorization field to be set in the request.

Automating the token renewal in the frontend

Now that the backend is updated, it’s time to add the token-renewal mechanism to the frontend.

Luckily, we’ll only have to update two classes: The AuthenticationResponse DTO (in the frontend) and the authentication service class. Let’s begin with the DTO class. You need to add the futureToken variable to it:

export class AuthenticationResponse
{
    token: string;
    futureToken: string;
    
    constructor() {}
}

Now to the service! Start by updating the setStoredToken method so that it puts both tokens, the current and future one, in the local storage:

setStoredToken(authResponse : AuthenticationResponse)
{
  localStorage.setItem("token", authResponse.token);
  localStorage.setItem("futureToken", authResponse.futureToken);
}

Then, update the logout method so that both tokens get deleted when a user logs out:

logOut(error:boolean)
{
  console.log('Logout');

  localStorage.removeItem("token");
  localStorage.removeItem("futureToken");
}

Now that that’s done, implement a new function that requests a new token from the backend for an already logged in user:

public renewToken() : Observable<TokenResponse>
{
  const httpOptions = {
    headers: new HttpHeaders(
      {
        'Authorization' : 'Bearer ' + localStorage.getItem("futureToken")
      }
    )
  };
  
  return this.httpClient.get<TokenResponse>(this.baseUri + "/", httpOptions)
}

Note how I use the future token to create the authorization header for this special request. This is necessary because the original token might have already expired at this point. Furthermore, in the last part, we’ve excluded requests made to the authorization address from requiring an authorization header. Therefore, we have to manually add it here.

Now everything’s ready to create a scheduler that automatically renews the token shortly before it expires. Start by creating a new variable:

private authScheduler: Observable<any> = interval(10000);

And then add a function that sets up the scheduler:

private scheduleReAuthentication()
{
  this.authScheduler.subscribe(() => {
    if (this.isLoggedIn())
    {
      const timeLeft = this.getStoredTokenExpiration().valueOf() - new Date().valueOf();

      if ((timeLeft / 1000) < 30)
      {
        this.renewToken().subscribe(
          (newToken : TokenResponse) => {
            this.setStoredToken(newToken);
            console.log('Token renewal was successful!');
          },
          error => {
            console.log('Unable to renew token: ' + error.error.error);
            this.logOut(true);
          });
      }
    }
  });
}

That new scheduler then gets started from the constructor:

constructor(private httpClient : HttpClient)
{
  this.scheduleReAuthentication();
}

In the code above, we set up a scheduler that gets activated every ten seconds. In the constructor, we called a method that sets it up. In this setup method, we check how much time is left before the current token expires. If it’s less than 30 seconds, the frontend starts a token renewal attempt.

If it manages to successfully renew the token, the user stays logged in and the frontend writes the new tokens to the local storage. Otherwise, the user gets logged out.

Proper error handling

As stated earlier in this series, I omitted the error so far. I mentioned that I wanted to cover that in the last part of this series. However, as it has become quite long already, and because the purpose of this series is to show you how you can implement JWT authorization in your app, I decided to completely omit the error handling.

The demo application

As promised, you can download the app here.

Note that the package only contains the source files. I deleted all other files to save space!

Table of contents

Part 1 – Basics of JWT and the project scaffolding
Part 2 – Configure Spring Security
Part 3 – JWT token generation, validation, and authentication
Part 4 – JWT authentication in an Angular frontend
Part 5 – Token renewal (You are here)

4 thoughts on “How to implement JWT authentication in Spring Security and Angular – Part 5

Leave your two cents, comment here!

This site uses Akismet to reduce spam. Learn how your comment data is processed.