Refresh Token Using Axios in React
Understanding Authentication Tokens and Token Refresh
Authentication tokens, like JSON Web Tokens (JWT), validate users in web apps and expire after a set time, requiring users to refresh them for uninterrupted access to protected resources.
Token refresh exchanges an expired token for a new one using a long-lived refresh token, eliminating the need for users to log in again by sending a request with the refresh token to obtain new access tokens.
Steps
Install axios via npm or yarn.
We create an Axios instance named
api
with a base URL.We define a
refreshToken
function responsible for obtaining a new token from the server by making a POST request to/refresh-token
.We use a request interceptor to add the token to outgoing requests.
We use a response interceptor to handle token expiration errors (status code 401).
If a token expiration error occurs, we attempt to refresh the token and retry the original request with the new token.
Now, let's set up Axios and define our interceptor functions for token refresh. Here's a basic setup in TypeScript:
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
});
// Function to refresh token
async function refreshToken(): Promise<string | null> {
try {
const response = await axios.post('/refresh-token');
const newToken = response.data.accessToken;
// Save the new token to local storage
localStorage.setItem('accessToken', newToken);
return newToken;
} catch (error) {
console.error('Error refreshing token:', error);
return null;
}
}
// Request interceptor for adding the token to requests
api.interceptors.request.use(
async (config: AxiosRequestConfig) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor for token refresh
api.interceptors.response.use(
(response: AxiosResponse) => {
return response;
},
async (error) => {
const originalRequest = error.config;
if (error.response && error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const newToken = await refreshToken();
if (newToken) {
// Retry the original request with the new token
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return axios(originalRequest);
}
}
return Promise.reject(error);
}
);
export default api;
Explanation of _retry
Flag
The _retry
flag prevents an infinite loop of token refresh attempts for a single request. It ensures that a request is retried only once after a token refresh, avoiding potential issues with continuously failing requests.
Why Refresh Function Uses axios
Instead of api
Instance
The refreshToken
function bypasses the api
instance declared at the start of the file due to the following reasons:
Interception Avoidance: Using
axios
directly in therefreshToken
function prevents interception by Axios interceptors, ensuring a clean request for fetching a new access token.Circular Dependency Prevention: Directly using
axios
avoids circular dependencies between theapi
instance and therefreshToken
function, which could lead to module loading issues.
This approach maintains a clear separation of concerns, allowing the token refresh mechanism to function smoothly without interference from interceptors or dependencies.
Now, for making API calls hooked up to the refresh flow, you can simply import the API object and use it how you would normally use the axios instance:
import api from './api';
async function fetchData() {
try {
const response = await api.get('/data');
console.log(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData();
In this example, the fetchData
function utilizes the api
instance to fetch data from the /data
endpoint. If an error occurs during the request, it is caught and logged.
Instead of creating a separate instance we can also use the global instance while initialising the interceptors, in which case we can make API calls with axios itself.
Why Use Local Storage or Cookies to Store Tokens?
It's better to store tokens (both access and refresh tokens) in local storage or cookies due to security reasons:
Protection against XSS attacks: Storing tokens in local storage or cookies helps mitigate the risk of cross-site scripting (XSS) attacks. Access to tokens stored in local storage or cookies is limited to the domain from which they originated, reducing the likelihood of them being accessed by malicious scripts injected from other domains.
Prevention of CSRF attacks: Cookies can be configured with the
HttpOnly
flag, which prevents them from being accessed by client-side scripts. This mitigates the risk of cross-site request forgery (CSRF) attacks, where an attacker tricks a user's browser into making unauthorized requests by exploiting their active session.Persistent authentication: Tokens stored in local storage or cookies persist across browser sessions, providing a seamless user experience. Users remain authenticated even after refreshing the page or closing the browser, reducing the need for frequent logins.
Subscribe to my newsletter
Read articles from Vaisakh Np directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Vaisakh Np
Vaisakh Np
A web developer looking to learn and build the most breathtaking projects. Interested in all things Javascript/Typescript. Hopping from one framework to another.