Spring Boot Refresh Token with JWT example

In previous post, we’ve known how to build Token based Authentication & Authorization with Spring Security & JWT. This tutorial will continue to make JWT Refresh Token in the Java Spring Boot Application. You can know how to expire the JWT, then renew the Access Token with Refresh Token.

Related Posts:

Deployment: Deploy Spring Boot App on AWS – Elastic Beanstalk

The code in this post bases on previous article that you need to read first:
Spring Boot Token based Authentication with Spring Security & JWT


Overview of Spring Boot Refresh Token with JWT example

We already have a Spring Boot application in that:

  • User can signup new account, or login with username & password.
  • By User’s role (admin, moderator, user), we authorize the User to access resources

With APIs:

MethodsUrlsActions
POST/api/auth/signupsignup new account
POST/api/auth/signinlogin an account
GET/api/test/allretrieve public content
GET/api/test/useraccess User’s content
GET/api/test/modaccess Moderator’s content
GET/api/test/adminaccess Admin’s content

For more details, please visit this post.

We’re gonna add Token Refresh to this Spring Boot – Spring Security Project.
The final result can be described with following requests/responses:

– Send /signin request, return response with refreshToken.

spring-boot-refresh-token-jwt-example-signin

– Access resource successfully with accessToken.

spring-boot-refresh-token-jwt-example-access-successful

– When the accessToken is expired, user cannot use it anymore.

spring-boot-refresh-token-jwt-example-expire-token

– Send /refreshtoken request, return response with new accessToken.

spring-boot-refresh-token-jwt-example-token-refresh-request

– Access resource successfully with new accessToken.

spring-boot-refresh-token-jwt-example-new-access-token

– Send an expired Refresh Token.

spring-boot-refresh-token-jwt-example-expire-refresh-token

– Send an inexistent Refresh Token.

spring-boot-refresh-token-jwt-example-refresh-token-not-in-database

– Axios Client to check this: Axios Interceptors tutorial with Refresh Token example

– Or using React Client:

Flow for Spring Boot Refresh Token with JWT

The diagram shows flow of how we implement Authentication process with Access Token and Refresh Token.

spring-boot-refresh-token-jwt-example-flow

– A legal JWT must be added to HTTP Authorization Header if Client accesses protected resources.
– A refreshToken will be provided at the time user signs in.

How to Expire JWT Token in Spring Boot

The Refresh Token has different value and expiration time to the Access Token.
Regularly we configure the expiration time of Refresh Token larger than Access Token’s.

Open application.properties for configuring App properties:

# Spring Datasource, Spring Data...

# App Properties
bezkoder.app.jwtSecret= bezKoderSecretKey
bezkoder.app.jwtExpirationMs= 3600000
bezkoder.app.jwtRefreshExpirationMs= 86400000

## For test
#bezkoder.app.jwtExpirationMs= 60000
#bezkoder.app.jwtRefreshExpirationMs= 120000

Update JwtUtils class. Now it has 3 main funtions:

  • generate a JWT from username
  • get username from JWT
  • validate a JWT: JWT Access Token is expired with ExpiredJwtException

security/jwt/JwtUtils.java

package com.bezkoder.spring.security.jwt.security.jwt;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.bezkoder.spring.security.jwt.security.services.UserDetailsImpl;

import io.jsonwebtoken.*;

@Component
public class JwtUtils {
  private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

  @Value("${bezkoder.app.jwtSecret}")
  private String jwtSecret;

  @Value("${bezkoder.app.jwtExpirationMs}")
  private int jwtExpirationMs;

  public String generateJwtToken(UserDetailsImpl userPrincipal) {
    return generateTokenFromUsername(userPrincipal.getUsername());
  }

  public String generateTokenFromUsername(String username) {
    return Jwts.builder().setSubject(username).setIssuedAt(new Date())
        .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(SignatureAlgorithm.HS512, jwtSecret)
        .compact();
  }

  public String getUserNameFromJwtToken(String token) {
    return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
  }

  public boolean validateJwtToken(String authToken) {
    try {
      Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
      return true;
    } catch (SignatureException e) {
      logger.error("Invalid JWT signature: {}", e.getMessage());
    } catch (MalformedJwtException e) {
      logger.error("Invalid JWT token: {}", e.getMessage());
    } catch (ExpiredJwtException e) {
      logger.error("JWT token is expired: {}", e.getMessage());
    } catch (UnsupportedJwtException e) {
      logger.error("JWT token is unsupported: {}", e.getMessage());
    } catch (IllegalArgumentException e) {
      logger.error("JWT claims string is empty: {}", e.getMessage());
    }

    return false;
  }

}

Refresh Token Request and Response

Update the payloads for our RestAPIs:
– Requests:

  • TokenRefreshRequest: { refreshToken }

– Responses:

  • JwtResponse: { accessToken, type, refreshToken, id, username, email, roles }
  • MessageResponse: { message }
  • TokenRefreshResponse: { accessToken, type, refreshToken }

payload/request/TokenRefreshRequest.java

package com.bezkoder.spring.security.jwt.payload.request;

import javax.validation.constraints.NotBlank;

public class TokenRefreshRequest {
  @NotBlank
  private String refreshToken;

  public String getRefreshToken() {
    return refreshToken;
  }

  public void setRefreshToken(String refreshToken) {
    this.refreshToken = refreshToken;
  }
}

payload/response/JwtResponse.java

package com.bezkoder.spring.security.jwt.payload.response;

import java.util.List;

public class JwtResponse {
	private String token;
	private String type = "Bearer";
	private String refreshToken;
	private Long id;
	private String username;
	private String email;
	private List<String> roles;

	public JwtResponse(String accessToken, String refreshToken, Long id, String username, String email, List<String> roles) {
		this.token = accessToken;
		this.refreshToken = refreshToken;
		this.id = id;
		this.username = username;
		this.email = email;
		this.roles = roles;
	}

	// getters and setters
}

payload/response/TokenRefreshResponse.java

package com.bezkoder.spring.security.jwt.payload.response;

public class TokenRefreshResponse {
  private String accessToken;
  private String refreshToken;
  private String tokenType = "Bearer";

  public TokenRefreshResponse(String accessToken, String refreshToken) {
    this.accessToken = accessToken;
    this.refreshToken = refreshToken;
  }

  // getters and setters
}

Renew JWT Token in Spring Boot

In the AuthController class, we:

  • update the method for /signin endpoint with Refresh Token
  • expose the POST API for creating new Access Token from received Refresh Token

controllers/AuthController.java

@RestController
@RequestMapping("/api/auth")
public class AuthController {
  @Autowired
  AuthenticationManager authenticationManager;

  @Autowired
  JwtUtils jwtUtils;

  @Autowired
  RefreshTokenService refreshTokenService;

  ...
  @PostMapping("/signin")
  public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {

    Authentication authentication = authenticationManager
        .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

    SecurityContextHolder.getContext().setAuthentication(authentication);

    UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();

    String jwt = jwtUtils.generateJwtToken(userDetails);

    List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
        .collect(Collectors.toList());

    RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());

    return ResponseEntity.ok(new JwtResponse(jwt, refreshToken.getToken(), userDetails.getId(),
        userDetails.getUsername(), userDetails.getEmail(), roles));
  }

  @PostMapping("/refreshtoken")
  public ResponseEntity<?> refreshtoken(@Valid @RequestBody TokenRefreshRequest request) {
    String requestRefreshToken = request.getRefreshToken();

    return refreshTokenService.findByToken(requestRefreshToken)
        .map(refreshTokenService::verifyExpiration)
        .map(RefreshToken::getUser)
        .map(user -> {
          String token = jwtUtils.generateTokenFromUsername(user.getUsername());
          return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
        })
        .orElseThrow(() -> new TokenRefreshException(requestRefreshToken,
            "Refresh token is not in database!"));
  }
}

In refreshtoken() method:

  • Firstly, we get the Refresh Token from request data
  • Next, get the RefreshToken object {id, user, token, expiryDate} from raw Token using RefreshTokenService
  • We verify the token (expired or not) basing on expiryDate field
  • Continue to use user field of RefreshToken object as parameter to generate new Access Token using JwtUtils
  • Return TokenRefreshResponse if everything is done
  • Or else, throw TokenRefreshException

Create Refresh Token Service

Refresh Token class

This class has one-to-one relationship with User class.

models/RefreshToken.java

package com.bezkoder.spring.security.jwt.models;

import java.time.Instant;

import javax.persistence.*;

@Entity(name = "refreshtoken")
public class RefreshToken {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  @OneToOne
  @JoinColumn(name = "user_id", referencedColumnName = "id")
  private User user;

  @Column(nullable = false, unique = true)
  private String token;

  @Column(nullable = false)
  private Instant expiryDate;

  //getters and setters

}

Refresh Token Repository

Before creating the service, we need RefreshTokenRepository with finder methods:

repository/RefreshTokenRepository.java

package com.accolite.pru.health.AuthApp.repository;

import com.accolite.pru.health.AuthApp.model.token.RefreshToken;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {

    @Override
    Optional<RefreshToken> findById(Long id);

    Optional<RefreshToken> findByToken(String token);

}

Refresh Token Service

After that, we have the RefreshTokenService service which uses RefreshTokenRepository above for providing several useful methods:

  • findByToken(): Find a RefreshToken based on the natural id i.e the token itself
  • createRefreshToken(): Create and return a new Refresh Token
  • verifyExpiration(): Verify whether the token provided has expired or not. If the token was expired, delete it from database and throw TokenRefreshException

security/services/RefreshTokenRepository.java

package com.bezkoder.spring.security.jwt.security.services;

import java.time.Instant;
import java.util.Optional;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.bezkoder.spring.security.jwt.exception.TokenRefreshException;
import com.bezkoder.spring.security.jwt.models.RefreshToken;
import com.bezkoder.spring.security.jwt.repository.RefreshTokenRepository;
import com.bezkoder.spring.security.jwt.repository.UserRepository;

@Service
public class RefreshTokenService {
  @Value("${bezkoder.app.jwtRefreshExpirationMs}")
  private Long refreshTokenDurationMs;

  @Autowired
  private RefreshTokenRepository refreshTokenRepository;

  @Autowired
  private UserRepository userRepository;

  public Optional<RefreshToken> findByToken(String token) {
    return refreshTokenRepository.findByToken(token);
  }

  public RefreshToken createRefreshToken(Long userId) {
    RefreshToken refreshToken = new RefreshToken();

    refreshToken.setUser(userRepository.findById(userId).get());
    refreshToken.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
    refreshToken.setToken(UUID.randomUUID().toString());

    refreshToken = refreshTokenRepository.save(refreshToken);
    return refreshToken;
  }

  public RefreshToken verifyExpiration(RefreshToken token) {
    if (token.getExpiryDate().compareTo(Instant.now()) < 0) {
      refreshTokenRepository.delete(token);
      throw new TokenRefreshException(token.getToken(), "Refresh token was expired. Please make a new signin request");
    }

    return token;
  }

  @Transactional
  public int deleteByUserId(Long userId) {
    return refreshTokenRepository.deleteByUser(userRepository.findById(userId).get());
  }
}

Handle Token Refresh Exception

Now we need to create TokenRefreshException class that extends RuntimeException.

exception/TokenRefreshException.java

package com.bezkoder.spring.security.jwt.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.FORBIDDEN)
public class TokenRefreshException extends RuntimeException {

  private static final long serialVersionUID = 1L;

  public TokenRefreshException(String token, String message) {
    super(String.format("Failed for [%s]: %s", token, message));
  }
}

Let’s do the final step. We’re gonna create a RestControllerAdvice.

advice/TokenRefreshException.java

package com.bezkoder.spring.security.jwt.advice;

import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;

import com.bezkoder.spring.security.jwt.exception.TokenRefreshException;

@RestControllerAdvice
public class TokenControllerAdvice {

  @ExceptionHandler(value = TokenRefreshException.class)
  @ResponseStatus(HttpStatus.FORBIDDEN)
  public ErrorMessage handleTokenRefreshException(TokenRefreshException ex, WebRequest request) {
    return new ErrorMessage(
        HttpStatus.FORBIDDEN.value(),
        new Date(),
        ex.getMessage(),
        request.getDescription(false));
  }
}

For more details about RestControllerAdvice, please visit:
@RestControllerAdvice example in Spring Boot

Conclusion

Congratulation!

Today we’ve learned a more interesting thing about JWT Refresh Token in a Spring Boot example.
Despite we wrote a lot of code, I hope you will understand the overall idea of the application, and apply it in your project at ease.

The code in this post bases on previous article that you need to read first:
Spring Boot Token based Authentication with Spring Security & JWT

You can test this Rest API with:
– Axios Client: Axios Interceptors tutorial with Refresh Token example
– Or React Client:

For understanding the architecture deeply and grasp the overview more easier:
Spring Boot Architecture for JWT with Spring Security

You can also know how to deploy Spring Boot App on AWS (for free) with this tutorial.

Happy learning! See you again.

Further Reading

More Practices:

Fullstack CRUD App:
Spring Boot + Vue.js example
Angular 8 + Spring Boot example
Angular 10 + Spring Boot example
Angular 11 + Spring Boot example
React + Spring Boot example

Source Code

You can find the complete source code for this tutorial on Github.

11 thoughts to “Spring Boot Refresh Token with JWT example”

  1. Thank you!
    This post is great, I’ve learned a lot from your posts, but can you write a post to share how vue handles jwt refresh?

  2. If I refresh the Token many times, I will get multiple Tokens, and these Tokens are different but it can be all authentic. Does this need to delete the old Token or not? If needed, how can I delete it?

    1. Hi, I don’t think that Client implements the refresh Token that way :). Client should refresh it only when the access Token is expired.

  3. Hello,

    When I post to the /refreshToken endpoint the new access and refresh tokens are equal to the previous, shouldn’t they be different?

    1. Hi, only refresh token is the same as the previous 🙂

      Generally, the refresh token has a long time to live. You don’t need to create a new refresh token everytime a user makes a /refreshtoken request. It helps us to reduce cost of database query (we store refresh token on a table).

  4. Hi I am getting a 403 forbidden error when trying to access /refreshtoken?

    Do you have any Idea on the silly mistake I could have made?

    1. I was an idiot, feel free just to delete my comments!

      I forgot the getters and setters on the TokenRefreshResponse!

  5. Hi ,
    Please if i want to refresh token with another method , and i want to decrease the backwards and forwards on the server by pushing notification to the front automatically when the token is expired , So thanks a lot if you write a tutorial and explain this when having time 😀

Leave a Reply

Your email address will not be published. Required fields are marked *