How to Refresh Json Web Tokens (JWT) using Axios Interceptors
Tags: javascript, webdev, beginners, tutorial
Published: 2022-06-18 15:57:26

Axios is a very popular http client in the community responsible for making http requests to third party services. And in the case of today, it will be used in two scenarios, the first is to make http requests that do not require any kind of authentication and the second is to refresh the token that is sent in the headers.

# Introduction

In a very quick and general way, axios interceptors are functions that are invoked whenever an http request is made with the axios instance being used. These functions are widely used to refresh our application's tokens, in order to allow the user to continue using the application without having to log in consecutively.

# Prerequisites

Before going further, you need:

- Node
- NPM

Also, it is expected that you already have a knowledge of how to consume an api and you have already used axios in the past.

# Getting Started

### Install dependencies

In the project you are currently working on, install the following dependencies:

```sh
npm install axios mem
```

After executing the command, we will have the following dependencies:

- `axios` - http client
- `mem` - performs the memorization of a function

With our dependencies installed we can move on to the next step.

### Configure Axios instances

As was said at the beginning of the article, we will have two instances of axios, the first one will be our public instance:

```js
// @/src/common/axiosPublic.js
import axios from "axios";

export const axiosPublic = axios.create({
  baseURL: "http://localhost:3333/api",
  headers: {
    "Content-Type": "application/json",
  },
});
```

As you may have noticed, the public instance is very basic and will be used to make http requests whose endpoints do not need any authentication (such as sign in, sign up, etc).

With our public instance configured, we can now move on to creating the role responsible for updating our token. As you may have guessed, let's import the `mem` and our public axios instance:

```js
// @/src/common/refreshToken.js
import mem from "mem";

import { axiosPublic } from "./axiosPublic";

// ...
```

Then we can create a function like this (this function can change according to your backend logic):


```js
// @/src/common/refreshToken.js
import mem from "mem";

import { axiosPublic } from "./axiosPublic";

const refreshTokenFn = async () => {
  const session = JSON.parse(localStorage.getItem("session"));

  try {
    const response = await axiosPublic.post("/user/refresh", {
      refreshToken: session?.refreshToken,
    });

    const { session } = response.data;

    if (!session?.accessToken) {
      localStorage.removeItem("session");
      localStorage.removeItem("user");
    }

    localStorage.setItem("session", JSON.stringify(session));

    return session;
  } catch (error) {
    localStorage.removeItem("session");
    localStorage.removeItem("user");
  }
};

// ...
```

As you may have noticed, first we will fetch our current session from our storage, then the current refresh token is sent to update it. If the session property does not appear in the response body or an error occurs, the user is logged out.

Otherwise, the session we have in storage is updated with the current session (which contains the respective access and refresh token).

Now this function is memoized, so that it is cached for about ten seconds:

```js
// @/src/common/refreshToken.js
import mem from "mem";

import { axiosPublic } from "./axiosPublic";

const refreshTokenFn = async () => {
  const session = JSON.parse(localStorage.getItem("session"));

  try {
    const response = await axiosPublic.post("/user/refresh", {
      refreshToken: session?.refreshToken,
    });

    const { session } = response.data;

    if (!session?.accessToken) {
      localStorage.removeItem("session");
      localStorage.removeItem("user");
    }

    localStorage.setItem("session", JSON.stringify(session));

    return session;
  } catch (error) {
    localStorage.removeItem("session");
    localStorage.removeItem("user");
  }
};

const maxAge = 10000;

export const memoizedRefreshToken = mem(refreshTokenFn, {
  maxAge,
});
```

Why is memoization done? The answer is very simple, if twenty http requests are made and all of them get a 401 (Unauthorized), we don't want the token to be refreshed twenty times. 

When we memoize the function, the first time it is invoked, the http request is made, but from then on, its response will always be the same because it will be cached for ten seconds (and without making the other nineteen http requests).

With the public instance of axios configured and the refresh token function memorized, we can move on to the next step.

### Configure the Interceptors

Unlike what we did before, now we are going to create an axios instance that will be responsible for making http requests to endpoints that require authentication.

First let's import the axios and our memoized refresh token function:

```js
// @/src/common/axiosPrivate.js
import axios from "axios";

import { memoizedRefreshToken } from "./refreshToken";

axios.defaults.baseURL = "http://localhost:3333/api";

// ...
```

First, let's configure the interceptor that will be invoked before the http request is made. This one will be responsible for adding the access token in the headers.

```js
// @/src/common/axiosPrivate.js
import axios from "axios";

import { memoizedRefreshToken } from "./refreshToken";

axios.defaults.baseURL = "http://localhost:3333/api";

axios.interceptors.request.use(
  async (config) => {
    const session = JSON.parse(localStorage.getItem("session"));

    if (session?.accessToken) {
      config.headers = {
        ...config.headers,
        authorization: `Bearer ${session?.accessToken}`,
      };
    }

    return config;
  },
  (error) => Promise.reject(error)
);

// ...
```

Now we can configure our interceptor that will be responsible for intercepting the response if our api sends a 401. And if it is sent, let's refresh the token and try again to make the http request but this time with the new access token.

```js
// @/src/common/axiosPrivate.js
import axios from "axios";

import { memoizedRefreshToken } from "./refreshToken";

axios.defaults.baseURL = "http://localhost:3333/api";

axios.interceptors.request.use(
  async (config) => {
    const session = JSON.parse(localStorage.getItem("session"));

    if (session?.accessToken) {
      config.headers = {
        ...config.headers,
        authorization: `Bearer ${session?.accessToken}`,
      };
    }

    return config;
  },
  (error) => Promise.reject(error)
);

axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const config = error?.config;

    if (error?.response?.status === 401 && !config?.sent) {
      config.sent = true;

      const result = await memoizedRefreshToken();

      if (result?.accessToken) {
        config.headers = {
          ...config.headers,
          authorization: `Bearer ${result?.accessToken}`,
        };
      }

      return axios(config);
    }
    return Promise.reject(error);
  }
);

export const axiosPrivate = axios;
```

# Conclusion

In today's article, a simple example of how the refresh token is done in an application was given. As you may have noticed in the article, `localStorage` was used, but with some code adjustments it is possible to adapt this same example to use cookies. And this strategy can be easily used with frontend frameworks like React, Vue, Svelte, Solid, etc.

I hope you liked it and I'll see you next time.