Handle Refresh Tokens with Axios

A guide to handling refresh tokens with Axios.

Sang Nguyen
JavaScript in Plain English

--

Photo by Shawn Lee on Unsplash

The refresh access token isn’t a new topic and we have many articles on it. It’s the same with love which isn’t a new topic either. But composers still write new love songs day in day out. That’s why I wrote a small article about how to refresh tokens with Axios.

Overview this case

Before coding, we need to know about the current task and the goal for it. In a popular system, to communicate between multiple services we need a “key” to make sure about security. In this article, I going to call this key a token. This token usually expires after a defined time. After this moment, this token can’t access anything. The client must get a new token to continue working with other services. We have many solutions to get new tokens automatically. One of them is province a refresh token for clients. When the access token expires, we can send a refresh token to the authentication service to get a new access token (and refresh token). I have made a simple photo for this concept below:

With this photo, we can define a basis for handling requests with some steps.

In the photo above, we need to take some steps to make sure we can get a new access token when the current access token expires. Use that new access token to continue where the previous request failed. In this article, I’ll apply this concept in Frontend with Axios.

Make a server for testing

To prepare for what we will work with Axios in this article, we need to spend a little time making a sample API. This API will help our testing easier. We have two APIs. One for access resource and one for refresh token. With API to access resources, if we call to it this wrong access token in the header, it will respond with an error with status code 401. About the second API, that API just responds to us with a new access token (to access the first API).

I’ll make this API with Express, you can use any other language or way to make this API. Just make two APIs with the requirements given above. Back to API, because I used Express so we need to install this package before.

yarn add express

In the next step, we just need to create file server.js. In this file, we will make a simple API server to support our testing. This content will like this.

To start this server, we just need run node server.js. To make sure about this API, I’ll make a simple test for it by calling some requests to it.

I’ve called some requests to this API and it worked well. Besides CURL, you can use Postman or any other tool to check it. Now, we can start code with Axios from now.

Handle expired token with Axios

Before writing the first line, we need to install Axios. Just a simple command to install this package.

yarn add axios

After installing this package, I’ll create a file client.js. We will make an example about handling expired tokens with Axios in this file. To start, we need to create an Axios instance and config some default setting for it (baseURL, header…) and create a function to make a request to API with this instance we created. This is my code for this step.

In this file, I’ve created an Axios instance with the method created from Axios. In function request, we will call a request to API access with axios_instance and log status code and data from API. Now, I’ll run this file with command node client.js.

It throws an error! Because we haven’t set access token in the header of this request. We can set it inside the function request. But I think it’s not a good way because we need to write the same thing in every function (if we need to make a new feature in the future). To support this case, Axios provides an awesome method is interceptors which help us intercept requests and responses. I’ll add one more variable is access_token in our client file and pass that variable’s value for the header. We just add this code above function request.

let access_token = 'good';axios_instance.interceptors.request.use((config) => {
config.headers['access_token'] = access_token;
return config;
});

Call it again. This is the result.

It worked! But this isn’t the main mission of this article. That is why I’ll modify the default value of access_token to “bad” and our job is to make it “good”. To make this, we need to handle case 401 from API. I’ve made a photo of the steps we will made to handle it above. Now, we only need to use method interceptors' responses to handle it. Here is a diagram and code to handle it.

In the code above, if the response is an error, we need some steps to handle it.

  • The first step is to get the config from the request input it in config. In the next step, we need to check the status code from the server. If status code is 401, we will call a request to API “/refresh” to get new access_token (good). I’ve made function refresh_token to do that. This function will return Axios.get(…). We just need to call it inside our Axios interceptors.
  • After that, we just need to pass a new value from API to variable access_token.
  • In the final step, we only need to return a new Axios instance with config of request before. For other cases, we handle by return Promise.reject(error), it will throw that errors like other errors.

Now, we can try to call recall our code again. Here is the result I got:

Finally, our request is a success after we have refreshed our access token. That is basic way to handle expired token with Axios. Here is full of what we make in client.js.

But it’s just a basic code. We still need to deal with some bad cases. In next part, we will handle it together.

Optimize some bad cases

Photo by Jackson Simmer on Unsplash

In the real case, I know we will deal with many bad cases. Here, I just write about two bad cases are infinity loop and handle multiple call to refresh API.

Infinity loop

In current code, we assume our code (client and sever) always working well. We call API, we got error 401, we call to the server to get a new access token, we use that access token to recall request, it’s successful! But… When we called to refresh the token, besides returning the new refresh token API continue to give us an error with 401. What’s happened? I’ll modify refresh API a bit with status 401 like this.

app.get('/refresh', (req, res) => {
res.status(401);
res.json({
access_token: 'good',
refresh_token: 'refresh'
})
});

Let’s see what we got from client.js

Boom!

This is an infinity loop problem I said. To fix that, we just need to add a flag for request config. When we got an error with status code 401, besides the current condition, I need to add one more condition for the check retry flag. If this flag isn’t in config, we will set this flag for config and continue call to refresh token API. Here are custom interceptors after editing:

That is the result I got with the new code. No infinity loop anymore.

Multiple requests with expired tokens at the same time

After solving the problem with infinity loop, we’re now going to deal with another trouble — Multiple requests at the same time. What’s this? This is an issue that happens when we call multiple requests with an expired token at the same time. It will make multiple request to refresh token. We can call function requests many times and see what I get.

It is really a big issue because we have at least three problems with this case:

  • The first problem is performance, in normal cases, we only need one request to refresh a new token, and requests after that can use that new token. But in this case, we call many requests to do the same things.
  • The second problem is synchronous data. Call same request for special thing like refresh token can make some problem about synchronous data. Because in application, beside just update data we may do other action with this (dispatch some events…)
  • The third problem is one of the important issues which makes our application crash. In a real-world project, authenticate service of refresh access token one time per refresh token. If we have many requests to refresh token in same time. Only one first request is handled by server. Rest of request will failed because server only handle one time per refresh token. It will make all other requests fail. These things may make our application throw some errors (can’t submit data, fetching data…). Or worsening, we may force users to log out if our application can’t get a new token even we got it!

We have many solutions for this problem. In this article, I just suggest a simple way. This creates a variable that will make a request for a refresh token. When we need a refresh token, besides calling Axios request directly we just need check this variable. If this variable is null, we will call function refresh_token and assign Axios.get for this variable. If this variable isn’t null, we just need awaiting for it to get an access token. After that, we only need one more step is set this variable to null. Let’s see this code below.

In line 1, I’ve added a new variable refreshing_token with default value is null. From lines 10 to 12, I replaced “let res = await refresh_token()” with three new lines. In line 10, I checked the value of refreshing_token, if its value is null I will assign the function refresh_token for it. In next line, we just waiting for result from it. After that, we can set value for this variable to null in line 12. This is the result I got after applying this change.

Only one request to get a new access token and all requests are successful with the new token.

Here is the full code of client.js:

Conclusion

In this article, we known about how to handle refresh tokens with Axios. Along with that, some solutions for some bad cases (such as infinity loop and multiple requests at the same time). I hope this article will be helpful for you. If you have other solutions and other bad cases, please tell me. You can read more about Axios in their document

Full source code in my Github.

Thanks for reading.

Photo by Annie on Unsplash

Contact with me via Linkedin or Twitter

More content at plainenglish.io. Sign up for our free weekly newsletter. Get exclusive access to writing opportunities and advice in our community Discord.

--

--