Question
Answer and Explanation
React itself does not inherently handle automatic token refresh. Token refresh is a server-side security feature typically managed within your application's authentication logic. However, React, being a front-end library, can be integrated with that logic to facilitate a seamless user experience.
Here’s how React applications typically handle automatic token refresh:
1. Token Storage:
- After a user logs in, the access token and refresh token (if using) are typically stored in secure client-side storage like `localStorage`, `sessionStorage`, or `cookies`. For added security, you can use an HTTP-only cookie for the refresh token.
2. HTTP Interceptors:
- You can use libraries like `axios` or `fetch` and their respective interceptor features to intercept HTTP requests before they're sent. These interceptors check if the access token is expired.
3. Token Expiry Check:
- Before making an API request, check if the access token is expired. You can do this by decoding the token (if it's a JWT) and comparing the 'exp' claim with the current time, or you can rely on HTTP 401 Unauthorized responses.
4. Token Refresh Logic:
- If the access token is expired (or upon receiving 401 status), use the refresh token to get a new access token and potentially a new refresh token from your authentication server. This process should be hidden from the user, keeping it behind the scenes.
5. Updating Stored Tokens:
- Once you have new access and refresh tokens, securely store them. Ensure your app uses these new tokens for all future API requests.
6. Retrying the Original Request:
- After successfully refreshing tokens, retry the original failed API request using the new access token. This ensures that the user's intended action succeeds without requiring manual intervention.
7. Handling Failures:
- If the token refresh process fails (e.g., the refresh token is also invalid), typically redirect the user to the login page.
8. Example using axios interceptors:
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'your_api_url',
});
axiosInstance.interceptors.request.use(config => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
let isRefreshing = false;
let refreshSubscribers = [];
const onRefreshed = (newToken) => {
refreshSubscribers.map(cb => cb(newToken));
refreshSubscribers = [];
};
axiosInstance.interceptors.response.use(response => response, async (error) => {
const originalRequest = error.config;
if (error.response && error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function(resolve){
refreshSubscribers.push((newToken) => {
originalRequest.headers.Authorization = `Bearer ${newToken}`;
resolve(axiosInstance(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
const refreshToken = localStorage.getItem('refreshToken');
try {
const response = await axios.post('your_refresh_endpoint', { refreshToken });
const { accessToken, refreshToken: newRefreshToken } = response.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', newRefreshToken);
onRefreshed(accessToken);
isRefreshing = false;
return axiosInstance(originalRequest);
} catch (refreshError) {
isRefreshing = false;
localStorage.clear();
window.location.href = '/login';
}
}
return Promise.reject(error);
});
export default axiosInstance;
By implementing these patterns, your React application can automatically handle token refresh, ensuring a smooth and secure authentication experience for users.