Optimizing Token Refreshing: Preventing Duplicate API Requests with Axios
--
In the realm of modern application development, secure authentication and authorization mechanisms are crucial for protecting user data and ensuring seamless user experiences. One such authentication method gaining popularity is JWT (JSON Web Tokens) token-based authentication. When implementing token-based authentication in application development, the access token assumes a vital role in accessing authorized APIs. Acting as a short-lived token, it serves to authenticate and authorize user requests for protected resources on the server. Access tokens have a limited lifespan, typically ranging from a few minutes to a few hours. Consequently, when the access token reaches its expiration, users are normally required to re-authenticate to obtain a fresh access token.
Here is where the refresh token comes into play. The refresh token is a long-lived token that is used to obtain a new access token without requiring the user to re-authenticate. When the access token expires, the client can send the refresh token to the server to request a new access token. The server verifies the refresh token and, if it is valid, issues a new access token, typically along with a new refresh token.
However, we can face the challenge of multiple API calls and handling the expiration of access tokens. When multiple APIs are called simultaneously after the access token has expired. Each API call receives a 401 UNAUTHORIZED response, leading to redundant token refresh attempts. This can not only impact the performance and efficiency of the application but also result in unnecessary API requests and potential strain on the server.
In this blog, we will explore an effective solution to mitigate this problem and reduce the number of API calls when dealing with access token expiration. We will dive into the details of how to leverage Axios, a popular HTTP client, and its interceptors function to optimize the token refreshing process. It is recommended that you have prior knowledge of Axios and Axios interceptors for better understanding.
General Approach:
In a typical React project, the home page is often designed to handle multiple API calls once the user logs in. However, a common challenge arises when the user’s session expires, causing all these API calls to return a 401 response, indicating unauthorized access. To overcome this issue, a general approach involves utilizing a refresh token to obtain a new access token.
To overcome this challenge, an effective approach is to implement an Axios response interceptor in the project. This interceptor allows for handling both successful and error responses from APIs. By utilizing this mechanism, we can address the issue of expired tokens and ensure a seamless user experience.
In the given code snippet, whenever an API call returns a 401 status code, the interceptor automatically triggers a request to obtain a new access token from the server. This eliminates the need for manual re-authentication and enables the user to seamlessly continue their session.
However, in the mentioned scenario where multiple API calls fail simultaneously with a 401 status code, the current implementation initiates a token refresh call for each failed request. To optimize this process, a more efficient approach would be to make the token refresh API call only once when the initial request fails. Subsequently, the obtained access token can be used for the remaining requests, reducing redundant token refresh calls and improving overall efficiency. This approach minimizes unnecessary network overhead and enhances the performance of the application.
Optimized Approach:
To address this scenario, it is necessary to maintain a record of whether the refresh token API has already been called. Additionally, we need to handle the situation where subsequent requests wait for the response from the refresh token API if it has already been initiated. To accomplish this, we can utilize state variables to keep track of whether the API has been called and to store the running refresh token promise. Implementing this approach can be facilitated through the use of closures.
const refreshExpiredTokenClosure = () => {
let isCalled = false;
let runningPromise = undefined;
return () => {
if (isCalled) {
return runningPromise;
} else {
isCalled = true;
runningPromise = obtainNewAccessToken();
return runningPromise;
}
};
};
The refreshExpiredTokenClosure
is a function, which returns a closure. This closure handles the logic of checking if the refresh token API has already been called and returns either the current running promise or initiates a new call to obtain a new access token.
- The
refreshExpiredTokenClosure
function initializes two variables:isCalled
andrunningPromise
. isCalled
keeps track of whether the refresh token API has been called before.runningPromise
holds the promise of obtaining a new access token.- The function returns an anonymous function, which serves as the closure.
- If
isCalled
istrue
, indicating that the refresh token API has already been called, it returnsrunningPromise
. - If
isCalled
isfalse
, meaning the refresh token API has not been called yet, it setsisCalled
totrue
, indicating that the API call is in progress. It then initiates theobtainNewAccessToken()
function to get a new access token and assigns the resulting promise torunningPromise
. Finally, it returnsrunningPromise
.
This helps avoid redundant API calls and ensures efficient handling of token expiration.
Here is the modified interceptor that effectively addresses the problem discussed earlier, along with the explained solution:
Conclusion:
This blog has addressed the challenge of handling token expiration in a token-based authentication system. By implementing a closure-based solution, we demonstrate how to optimize the handling of multiple failed API calls with a 401 UNAUTHORIZED status. This approach reduces unnecessary refresh token API calls, improving performance and efficiency. By following the proposed solution, developers can enhance their authentication systems and provide a smoother user experience.
I hope you found this blog post helpful for your project! If you have any suggestions for improving this post, I’d love to hear feedback in the comments.
Follow me on:
GitHub: https://github.com/tanmaythole
LinkedIn: https://www.linkedin.com/in/tanmay-thole-b82978175/