What is Silent Refresh and How to Implement It

Silent refresh is a mechanism that allows frontend applications to automatically request a new token when the current token is about to expire, without requiring user intervention. This approach enhances user experience by avoiding login interruptions due to token expiration.

Common Methods to Implement Silent Refresh

Token Expiry Management

  • After user login, the frontend typically stores access tokens (e.g., JWT) and refresh tokens.
  • Access tokens usually have a short validity period (e.g., 15 minutes), while refresh tokens have a longer validity period (e.g., 7 days).
  • The frontend proactively uses the refresh token to request a new access token from the backend when the access token is about to expire (e.g., 2 minutes remaining).

Interceptors

  • Frontend applications can use interceptors (e.g., in Axios or Fetch) to intercept all requests.
  • If the current access token is detected as expired, the interceptor first uses the refresh token to obtain a new access token, then resends the original request.
  • This ensures that users do not perceive any interruption, and the application maintains continuous operation.

Silent Refresh Logic

  • Set up a timer to automatically trigger a refresh operation before the token expires.
  • Regularly check the token’s validity, and automatically refresh it if it is about to expire to prevent user operations from being interrupted.
  • If the refresh token also expires, redirect the user to the login page.

Silent Authentication

  • For some frontend applications, silent authentication can be performed using a hidden iframe to complete token refresh in the background using SSO mechanisms, without affecting the user’s frontend operations.

Example of Axios Interceptor Implementation

Here is a complete code example of an Axios interceptor to handle JWT token auto-refresh and request retries. In this example, it is assumed that you already have a backend API that provides token refresh functionality.

1. Create Axios Instance

First, create an Axios instance and configure request and response interceptors.

1
import axios from "axios";
2
3
// Create Axios instance
4
const apiClient = axios.create({
5
baseURL: "https://api.example.com", // Replace with your API base URL
6
timeout: 10000,
7
});

2. Add Request Interceptor

Include the Authorization header with each request to attach the JWT access token.

1
// Add request interceptor
2
apiClient.interceptors.request.use(
3
(config) => {
4
// Add Authorization header with each request
5
const token = localStorage.getItem("token");
6
if (token) {
7
config.headers["Authorization"] = `Bearer ${token}`;
8
}
9
return config;
10
},
11
(error) => {
12
// Handle request error
13
return Promise.reject(error);
14
},
15
);

3. Add Response Interceptor

Handle 401 errors and use the refresh token to obtain a new access token if the token has expired.

1
// Add response interceptor
2
apiClient.interceptors.response.use(
3
(response) => {
4
// Directly return the response data
5
return response;
6
},
7
async (error) => {
8
const originalRequest = error.config;
9
10
// If response status code is 401 and original request has not been retried
11
if (
12
error.response &&
13
error.response.status === 401 &&
14
!originalRequest._retry
15
) {
16
originalRequest._retry = true;
17
try {
18
const refreshToken = localStorage.getItem("refreshToken");
19
if (refreshToken) {
20
// Send refresh token request
21
const { data } = await axios.post(
22
"https://api.example.com/auth/refresh",
23
{ token: refreshToken },
24
);
25
26
// Update token in local storage
27
localStorage.setItem("token", data.token);
28
29
// Update Authorization header
30
axios.defaults.headers.common["Authorization"] =
31
`Bearer ${data.token}`;
32
33
// Resend original request
34
originalRequest.headers["Authorization"] = `Bearer ${data.token}`;
35
return apiClient(originalRequest);
36
}
37
} catch (refreshError) {
38
// If refresh token also fails, redirect to login page
39
localStorage.removeItem("token");
40
localStorage.removeItem("refreshToken");
41
window.location.href = "/login";
42
}
43
}
44
45
// Handle other errors
46
return Promise.reject(error);
47
},
48
);
49
50
export default apiClient;

Code Explanation

  1. Axios Instantiation:

    Using axios.create() to create a custom Axios instance apiClient, specifying the base URL and other configurations.

  2. Request Interceptor:

    Before each request, check if a JWT access token exists and, if so, add it to the Authorization header.

  3. Response Interceptor:

    • Handle 401 Errors: When the server returns a 401 Unauthorized error, the token may have expired.
    • Refresh Token: If a refresh token exists, send a request to the refresh token API endpoint to get a new access token.
    • Update Token: Store the new token in local storage and update the default request headers in the Axios instance for subsequent requests.
    • Retry Original Request: Resend the original request with the new token.
  4. Refresh Token Failure:

    If the refresh token is invalid or expired, clear the tokens from local storage and redirect the user to the login page.

How to Use

In your application, you can use apiClient instead of the original Axios instance to send HTTP requests:

1
import apiClient from "./apiClient";
2
3
async function getUserData() {
4
try {
5
const response = await apiClient.get("/user/profile");
6
console.log(response.data);
7
} catch (error) {
8
console.error("Failed to get user data:", error);
9
}
10
}

This code uses the encapsulated apiClient to send requests, automatically handling token refresh and retry logic.

Summary

By implementing silent refresh, you can ensure that users are not interrupted by token expiration during their use of the application. This mechanism enhances user experience and makes the application more seamless.