CodeGym /Courses /Module 5. Spring /Lecture 106: Using Refresh Tokens and Their Configuration...

Lecture 106: Using Refresh Tokens and Their Configuration

Module 5. Spring
Level 18 , Lesson 5
Available

Access Token (access token) — is a key that lets a user interact with protected resources. However, for security reasons its lifetime is limited. For example, an Access Token might be valid for only 15 minutes. After the token expires, all protected requests will start returning 401 Unauthorized errors. In that case the user is forced to re-authenticate, which will clearly annoy them (and they probably won't give your app 5 stars in the App Store).

Solution: Refresh tokens

A Refresh token is meant to refresh an expired Access Token without requiring the user to re-enter credentials. This makes the user's interaction with the app smooth and the system more convenient.

How does it work?

  1. On successful authentication the server issues two tokens:
    • Access Token — for accessing protected resources.
    • Refresh Token — for obtaining a new Access Token when the old one expires.
  2. When the Access Token expires, the client (for example, your mobile or web app) sends the Refresh Token to the server.
  3. The server validates the Refresh Token and, if it's valid, issues a new Access Token.
  4. The user continues working without re-authenticating.

Configuring and using Refresh tokens in Spring

Let's build a Spring Boot app that uses Refresh tokens to renew Access Tokens. For this you need an existing app with JWT authentication. If you did the exercises from previous lectures, you already have a basic JWT setup. If not, I recommend going back and setting that up first.

1. Generating Refresh tokens

Start by changing the token issuance logic. Now our authentication endpoint will return two tokens.

Example token generation code:


@RestController
@RequestMapping("/auth")
public class AuthController {

    private final JwtService jwtService; // Service for working with JWT
    private final UserService userService;

    @PostMapping("/login")
    public ResponseEntity
    login(@RequestBody LoginRequest request) {
        // Check user's credentials
        User user = userService.authenticate(request.getUsername(), request.getPassword());

        // Generate Access and Refresh tokens
        String accessToken = jwtService.generateAccessToken(user);
        String refreshToken = jwtService.generateRefreshToken(user);

        // Return them in the response
        return ResponseEntity.ok(Map.of(
            "accessToken", accessToken,
            "refreshToken", refreshToken
        ));
    }
}

Note that we now use two different methods to generate Access and Refresh tokens. Access Token usually has a short lifetime (for example, 15 minutes), whereas Refresh Token is longer-lived (for example, 7 days).

2. Adding an endpoint to refresh tokens

Now we'll add an endpoint that accepts a Refresh Token and returns a new Access Token.

Implementation example:


@RestController
@RequestMapping("/auth")
public class AuthController {

    private final JwtService jwtService;

    @PostMapping("/refresh")
    public ResponseEntity<?> refresh(@RequestBody Map<String, String> body) {
        String refreshToken = body.get("refreshToken");

        // Validate the Refresh token
        if (!jwtService.isValidRefreshToken(refreshToken)) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Refresh Token");
        }

        // Generate a new Access Token
        String newAccessToken = jwtService.generateAccessTokenFromRefreshToken(refreshToken);

        return ResponseEntity.ok(Map.of(
            "accessToken", newAccessToken
        ));
    }
}

3. Validation and generation logic in JwtService

We'll add two new methods to JwtService. One for validating the Refresh token, and another for generating a new Access Token based on it.

Example:


@Service
public class JwtService {

    private final String secretKey = "your-secret-key";  // Secret key for signing tokens

    public boolean isValidRefreshToken(String refreshToken) {
        try {
            Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(refreshToken)
                .getBody();

            // Make sure it's actually a Refresh token
            String tokenType = claims.get("type", String.class);
            return "refresh".equals(tokenType);
        } catch (JwtException e) {
            return false;
        }
    }

    public String generateAccessTokenFromRefreshToken(String refreshToken) {
        Claims claims = Jwts.parser()
            .setSigningKey(secretKey)
            .parseClaimsJws(refreshToken)
            .getBody();

        String username = claims.getSubject();

        // Create a new Access Token
        return Jwts.builder()
            .setSubject(username)
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15 minutes
            .signWith(SignatureAlgorithm.HS512, secretKey)
            .compact();
    }
}

Here it's important to ensure the Refresh token is indeed a Refresh token (for example, by checking an extra "type" field in the payload).

4. Secure handling of Refresh tokens

Refresh tokens are more exposed because they live longer. Here are some recommendations to improve security:

  • Client storage: never store Refresh tokens in localStorage. Use HTTP-only cookies.
  • HTTPS protocol: always use a secure connection when transmitting tokens.
  • Token rotation: after using a Refresh token to get a new Access Token, you can generate a new Refresh Token and return it to the client. This reduces the chance of compromise.
  • Revoked token list: if a token leak is detected, you must be able to revoke the Refresh token. This is usually done by tracking valid tokens in a database.

5. Error handling

Common problems with Refresh tokens:

  • Expired token: return 401 Unauthorized and ask the user to re-authenticate.
  • Invalid token: if the token is tampered with, reject it.
  • Token forgery: use digital signatures (HMAC or RSA) to verify authenticity.

Example error handling:


if (!jwtService.isValidRefreshToken(refreshToken)) {
    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Refresh Token");
}

Practical applications

It's hard to imagine a modern app without Refresh tokens. For example:

  • In mobile apps, where it's important to keep the user logged in for long periods.
  • In web apps that need to support background requests (for example, fetching a new feed).
  • Employers value knowledge of Refresh tokens because it shows understanding of security principles, user experience, and token handling.

The official Spring Security documentation on working with JWT and OAuth2 is available here.

Now you're ready to use Refresh tokens in your apps and can significantly improve both user experience and security!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION