Refresh Token with React and Spring Boot Integration

Last Updated : 14 Oct, 2026

In token-based authentication, an access token is used to authenticate API requests. However, access tokens are generally kept short-lived for security. A refresh token allows the application to obtain a new access token after the current one expires without requiring the user to log in again.

  • Access Token: Short-lived token sent with protected API requests.
  • Refresh Token: Longer-lived credential used to request a new access token.

Refresh Token

A refresh token is a credential issued along with or after an access token when a user successfully authenticates. When the access token expires, the client sends the refresh token to a dedicated authentication endpoint to obtain a new access token.

frame_4125

The refresh token itself should not be used to access protected application APIs. Its purpose is to obtain a new access token.

Why Do We Need Refresh Tokens?

If an application uses only long-lived access tokens, a stolen token may remain usable for a long period. If access tokens are made short-lived without refresh tokens, users may need to log in repeatedly.

Refresh tokens address this by separating the two responsibilities:

Access TokenRefresh Token
Used to access protected APIsUsed to obtain a new access token
Usually short-livedUsually longer-lived
Sent frequently with API requestsSent only to the refresh endpoint
Contains authorization information when using JWTMay be an opaque random token or JWT
Expiration requires refresh/re-authenticationExpiration generally requires login again

How Refresh Token Works Between React and Spring Boot

In a React and Spring Boot application, React stores or manages the authentication state and sends the access token with protected API requests, while Spring Boot validates the token before allowing access. When the access token expires, React sends the refresh token to Spring Boot, which validates it and issues a new access token so the user can continue without logging in again.

The overall process is:

frame_4209

Spring Boot Backend Implementation

We will first create the authentication backend that generates and validates access and refresh tokens.

Technologies Used

Step 1: Create the Spring Boot Project

Create a project using Spring Initializr with:

  • Project: Maven
  • Language: Java
  • Packaging: Jar
  • Java: 17 or later
  • Group: com.example
  • Artifact: refresh-token

Add these dependencies:

  • Spring Web
  • Spring Security
  • Spring Data JPA
  • MySQL Driver
  • Validation

For JWT support, add an appropriate JWT library to the Maven project.

Screenshot-2026-09-29-163955

Step 2: Configure MySQL

Create a database:

CREATE DATABASE authdb;

Configure application.properties:

spring.datasource.url=jdbc:mysql://localhost:3306/authdb
spring.datasource.username=root
spring.datasource.password=your_password

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Replace your_password with your MySQL password.

Keep JWT signing keys/secrets outside source code in real applications, such as environment variables or a secrets manager.

Step 3: Create the User Entity

Create User.java:

Java
package com.example.refreshtoken.model;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(nullable = false) private String password;

    public User() {}

    public Long getId() { return id; }

    public String getUsername() { return username; }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public String getPassword() { return password; }

    public void setPassword(String password)
    {
        this.password = password;
    }
}

Passwords should be stored using a secure password hash such as BCrypt, not as plain text.

Step 4: Create the Refresh Token Entity

Refresh tokens can be stored in the database so that the server can expire or revoke them.

Java
package com.example.refreshtoken.model;

import jakarta.persistence.*;
import java.time.Instant;

@Entity
public class RefreshToken {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(nullable = false) private Instant expiryDate;

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

    public RefreshToken() {}

    public Long getId() { return id; }

    public String getToken() { return token; }

    public void setToken(String token)
    {
        this.token = token;
    }

    public Instant getExpiryDate() { return expiryDate; }

    public void setExpiryDate(Instant expiryDate)
    {
        this.expiryDate = expiryDate;
    }

    public User getUser() { return user; }

    public void setUser(User user) { this.user = user; }
}

For production systems, storing a hash of the refresh token instead of the raw token provides additional protection if the database is exposed.

Step 5: Create the Refresh Token Repository

Java
package com.example.refreshtoken.repository;

import com.example.refreshtoken.model.RefreshToken;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RefreshTokenRepository
    extends JpaRepository<RefreshToken, Long> {

    Optional<RefreshToken> findByToken(String token);

    void deleteByUserId(Long userId);
}

The repository allows the application to find, validate, and revoke stored refresh tokens.

Step 6: Create the Refresh Token Service

The service is responsible for creating and validating refresh tokens.

Java
package com.example.refreshtoken.service;

import com.example.refreshtoken.model.RefreshToken;
import com.example.refreshtoken.model.User;
import com.example.refreshtoken.repository.RefreshTokenRepository;
import java.time.Instant;
import java.util.UUID;
import org.springframework.stereotype.Service;

@Service
public class RefreshTokenService {

    private final RefreshTokenRepository repository;

    public RefreshTokenService(
        RefreshTokenRepository repository)
    {
        this.repository = repository;
    }

    public RefreshToken createRefreshToken(User user)
    {

        RefreshToken refreshToken = new RefreshToken();

        refreshToken.setUser(user);
        refreshToken.setToken(UUID.randomUUID().toString());

        refreshToken.setExpiryDate(
            Instant.now().plusSeconds(7 * 24 * 60 * 60));

        return repository.save(refreshToken);
    }

    public RefreshToken verifyExpiration(RefreshToken token)
    {

        if (token.getExpiryDate().isBefore(Instant.now())) {
            repository.delete(token);
            throw new RuntimeException(
                "Refresh token expired");
        }

        return token;
    }

    public RefreshToken findByToken(String token)
    {
        return repository.findByToken(token).orElseThrow(
            () > new RuntimeException(
                "Invalid refresh token"));
    }
}

In this example, the refresh token is valid for 7 days. The actual lifetime should be selected according to the application's security requirements.

Step 7: Create JWT Service

The JWT service generates access tokens after authentication and refresh.

A simplified structure is:

Java
@Service
public class JwtService {

    public String generateAccessToken(User user) {

        // Create and sign a short-lived JWT
        // containing the required user claims.

        return "generated-access-token";
    }
}

In the real implementation, replace the placeholder with your JWT library's signing logic and configure a short expiration time.

For example:

Access Token  -> 15 minutes
Refresh Token -> 7 days

These are example values rather than universal requirements.

Step 8: Login and Generate Tokens

After successful authentication, generate both credentials:

Java
@PostMapping("/login")
public TokenResponse
login(@RequestBody LoginRequest request)
{

    User user = authenticateUser(request.getUsername(),
                                 request.getPassword());

    String accessToken
        = jwtService.generateAccessToken(user);

    RefreshToken refreshToken
        = refreshTokenService.createRefreshToken(user);

    return new TokenResponse(accessToken,
                             refreshToken.getToken());
}


A successful response can look like:

{
"accessToken": "eyJhbGciOi...",
"refreshToken": "17af563b-..."
}

The access token is then used for authenticated API requests.

Step 9: Create the Refresh Token Endpoint

Create a request DTO:

Java
public class RefreshTokenRequest {

    private String refreshToken;

    public String getRefreshToken() {
        return refreshToken;
    }

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

Then create the endpoint:

Java
@PostMapping("/refresh")
public TokenResponse
refresh(@RequestBody RefreshTokenRequest request)
{

    RefreshToken refreshToken
        = refreshTokenService.findByToken(
            request.getRefreshToken());

    refreshTokenService.verifyExpiration(refreshToken);

    String newAccessToken = jwtService.generateAccessToken(
        refreshToken.getUser());

    return new TokenResponse(newAccessToken,refreshToken.getToken());
}

Step 10: Configure Spring Security

The login and refresh endpoints must be accessible without an existing valid access token.

For example:

Java
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http)
    throws Exception
{

    http.csrf(csrf -> csrf.disable())
        .authorizeHttpRequests(
            auth
            -> auth.requestMatchers("/api/auth/login","/api/auth/refresh")
                   .permitAll()
                   .anyRequest()
                   .authenticated());

    return http.build();
}

In a complete JWT application, the security chain also contains the JWT authentication filter responsible for validating bearer access tokens.

React Frontend Implementation

React now needs to send the access token with protected requests and obtain another access token when necessary.

Step 11: Create an Axios Instance

Create src/services/api.js:

JavaScript
import axios from "axios";

const api = axios.create({
  baseURL: "http://localhost:8080/api",
});

export default api;

Step 12: Add the Access Token to Requests

An Axios request interceptor can attach the current access token:

JavaScript
api.interceptors.request.use((config) => {

  const accessToken =
    sessionStorage.getItem("accessToken");

  if (accessToken) {
    config.headers.Authorization =
      `Bearer ${accessToken}`;
  }

  return config;
});

For a production browser application, token-storage strategy needs careful security consideration. Avoid treating browser storage of long-lived refresh tokens as the default safe design.

Step 13: Refresh an Expired Access Token

A response interceptor can detect an authentication failure and request another access token.

JavaScript
api.interceptors.response.use(
    (response) => response,

    async (error) => {
        const originalRequest = error.config;

        if (error.response?.status === 401
            && !originalRequest._retry) {

            originalRequest._retry = true;

            const refreshToken
                = sessionStorage.getItem("refreshToken");

            const response = await axios.post(
                "http://localhost:8080/api/auth/refresh",
                {refreshToken});

            const newAccessToken
                = response.data.accessToken;

            sessionStorage.setItem("accessToken",
                                   newAccessToken);

            originalRequest.headers.Authorization
                = `Bearer ${newAccessToken}`;

            return api(originalRequest);
        }

        return Promise.reject(error);
    });

For a robust application, also handle failed refresh attempts, simultaneous 401 responses, and token revocation.

Step 14: Handle Refresh Token Expiration

If the refresh token itself is invalid, revoked, or expired, the application should end the authenticated session and require the user to authenticate again.

For example:

try {
  // refresh request
} catch (refreshError) {

  sessionStorage.removeItem("accessToken");
  sessionStorage.removeItem("refreshToken");

  window.location.href = "/login";
}

This prevents the application from repeatedly trying to use an unusable refresh token.

Refresh Token Rotation

A stronger design uses refresh token rotation.

Instead of returning the same refresh token every time:

Refresh Token A -> Refresh Request -> Invalidate Token A -> New Access Token + Refresh Token B

If Token A is subsequently reused, the server can treat that as a possible token-reuse event and revoke the associated session/token family.

For security-sensitive applications, rotation and server-side revocation are preferable to indefinitely reusing the same refresh token.

Logout and Refresh Token Revocation

Logging out should invalidate the server-side refresh token/session.

For example:

User clicks Logout -> React sends logout request -> Spring Boot finds refresh token/session -> Refresh token is revoked/deleted -> Client credentials are cleared -> User returns to Login

Deleting only the access token from React is insufficient if a valid refresh credential remains usable.

  • Keep access tokens short-lived.
  • Give refresh tokens a defined expiration time.
  • Revoke refresh tokens during logout where the architecture supports it.
  • Consider refresh-token rotation.
  • Store server-side refresh-token hashes rather than raw values.
  • Use HTTPS in production.
  • Never put signing secrets directly in frontend code.
  • Validate token expiration and revocation server-side.
  • For browser applications, consider keeping refresh credentials in Secure, HttpOnly cookies rather than exposing long-lived refresh tokens to JavaScript.
  • If cookies are used for authentication/refresh, configure appropriate SameSite behavior and CSRF protections.
  • Do not blindly refresh every 401; distinguish expired authentication from authorization failures and other authentication errors.
Comment

Explore