In modern mobile applications, API requests
are the backbone for fetching data, user interactions, and other crucial functionalities. One common challenge arises when your app faces an expired token, which results in a 401 Unauthorized
error.
In this article, we’ll walk you through handling such errors silently using Axios interceptors without disrupting the user experience. Instead of forcing the user to re-login, we can refresh the token and retry the failed requests automatically.
Why Refresh the Token?
Tokens are often used to authenticate users, and these tokens can expire over time, leading to a 401 Unauthorized
response from your API. When this happens, we can refresh the token in the background and retry the failed requests. This method avoids disrupting the user with unnecessary logins or errors.
Setting Up Axios for API Requests
To start, we will define four basic API request functions: getRequest
, postRequest
, updateRequest
, and deleteRequest
. Each function handles its respective HTTP method.
export const getRequest = async (path, params) => {};
export const postRequest = async (path, params) => {};
export const updateRequest = async (path, params) => {};
export const deleteRequest = async (path, params) => {};
These functions will use Axios to send the requests, but we need to manage what happens when a 401 error
occurs.
Handling Token Refresh
To handle token refresh and avoid multiple refresh requests at the same time, we’ll create a flag to manage this process and a queue to store requests that fail due to a 401 Unauthorized
error. The queue allows us to retry them once the token has been refreshed.
Step 1: Define the Flag and Queue
First, we’ll define a flag to prevent multiple token refresh requests from happening simultaneously, as well as a queue to hold failed requests.
// Flag to prevent multiple token refresh requests
let isRefreshing = false;
// Create a list to hold the request queue
const refreshAndRetryQueue = [];
Step 2: Set Up the Axios Interceptor
Next, we add an Axios interceptor to catch any 401 Unauthorized
errors. If the request fails due to a 401
, we attempt to refresh the token, retry the original request, and resolve any queued requests with the new token.
// Axios interceptor
axios.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response && error.response.status === 401) {
if (!isRefreshing && !originalRequest._retry) {
isRefreshing = true;
try {
// Refresh the access token
const newAccessToken = await refreshToken();
if (newAccessToken) {
originalRequest._retry = true;
// Update the request headers with the new access token
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
// Retry all requests in the queue with the new token
refreshAndRetryQueue.forEach(({ config, resolve, reject }) => {
axios
.request(config)
.then((response) => resolve(response))
.catch((err) => reject(err));
});
// Clear the queue
refreshAndRetryQueue.length = 0;
// Retry the original request
return axios(originalRequest);
}
} catch (err) {
// Handle token refresh error: clear storage or redirect to login
} finally {
isRefreshing = false;
}
}
// Add the original request to the queue
return new Promise((resolve, reject) => {
refreshAndRetryQueue.push({ config: originalRequest, resolve, reject });
});
}
return Promise.reject(error);
}
);
Step 3: Refresh Token Function
You’ll need to implement the refreshToken
function, which sends a request to your server to get a new access token. This function will handle the actual token refresh process.
export const refreshToken = async () => {
// Make API call to refresh the token
try {
const response = await axios.post('/auth/refresh-token', {
// Provide necessary refresh token details
});
const { accessToken } = response.data;
// Save the new token for future requests
// e.g., localStorage.setItem('accessToken', accessToken);
return accessToken;
} catch (error) {
console.error("Failed to refresh token", error);
return null;
}
};
Step 4: Integrating Token Refresh into API Requests
Now that we have our interceptor set up, we can modify our getRequest
, postRequest
, updateRequest
, and deleteRequest
functions to handle the token refresh logic seamlessly. This is how you’d handle a 401 Unauthorized
error in the getRequest
function:
export const getRequest = async (path, params) => {
try {
const response = await axios.get(path, {
params,
headers: {
'Access-Control-Allow-Origin': '*',
Authorization: `Bearer ${localStorage.getItem('accessToken')}`, // Get token from storage
},
});
if (response.status >= 200 && response.status < 300) {
return await response.data;
} else {
throw new Error(`${response.status}`);
}
} catch (error) {
// Log error and handle 401 scenarios
callAnalyticsAndToastMsg('GET', path, error);
if (error.response.status === 401) {
callAnalyticsAndToastMsg('GET', path, error);
}
throw error;
}
};
Similarly, this pattern can be applied to postRequest
, updateRequest
, and deleteRequest
.
Step 5: Handling Multiple Failed Requests
If multiple requests fail due to token expiration, they will be queued and retried once the token is successfully refreshed. The Axios interceptor ensures that no user action is required to refresh the token or retry the requests. This happens automatically in the background, keeping the user experience smooth and seamless.
Conclusion
By leveraging Axios interceptors and refreshing tokens automatically, you can handle 401 Unauthorized errors without disrupting the user experience. This approach ensures that expired tokens are refreshed in the background and all failed requests are retried automatically. Implementing this strategy keeps your app secure while providing a frictionless user experience.
Try integrating this method into your React Native API handling code and enjoy a smoother flow for your users!
Top comments (0)