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

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

Alternatively, you can also automatically log out users, if they haven’t interacted with the page for a set 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. However, 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 a check is performed that validates, whether the futureToken variable is true. If it’s false, the validity and validFrom variables are generated as before. If, however, it’s true, a second token, the futureToken, gets created. 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, that requests a token, we’ll also have to update the AuthenticationResponse class. Simply 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, it is not so far being created anywhere. Furthermore, the frontend has no way to renew a user’s token with the future token.

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

// ... 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 future token gets generated. Both tokens are returned to the requesting party.

To enable 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, no new login is performed. Instead, the username is read 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 mechanism to the frontend, that’ll automatically renew a logged in user’s token.

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’ll simply need to add the futureToken variable to it, just like you did in the backend:

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, into 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’ll request 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 we explicitly used 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, if you remember, in the last part we excluded requests, that are made to the authorization address, from getting an authorization header. Therefore, we have to manually add it here.

Now everything’s ready to create a scheduler that’ll automatically try to renew the token shortly before it expires. For that reason, create a new variable:

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

And then add a function that sets the scheduler up:

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, a token renewal attempt is started.

If the token is renewed successfully, the user stays logged in and the new tokens get saved to the local storage. Otherwise, the user gets logged out.

Proper error handling

As stated earlier in this series, the error handling was omitted so far. I said, that I wanted to cover that in the last part of this series. However, it has become so 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 omit the error handling completely.

The demo application

As promised, you can download the app here.

Note, that the package only contains the source files. All other files were deleted 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!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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