Refreshing access token with axios

2021-07-08

When implementing the idea of access tokens and refresh tokens, it is important to refetch a new access token once the clients current access token expires. This provides a seemless experience for the user, without requiring them to log in every time their access token expired.

There are different ways to handle refetching such as setting a timer to exactly when the access token expires and refetching a new one but I found that handling 403 (or whatever error you send when a user has an expired token) provides a seemless experience.

Let's say the clients access token expires. They send a request to a auth protected resource. There should be some middleware in your server that will be able to read the access token from the header and verify it. If it's good, then the client can access the resource but if not... well we should throw an 403 error at the client.


export const auth = (req: Request, res: Response, next: NextFunction) => {
    const token = req.headers.authorization;
    try {
        if (!token || token === 'undefined') throw Error("You are not authorized");
        const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        res
            .status(403)
            .json({ status: { text: err.message, severity: "error" } });
    }
};

The client should see that the request failed and then refetch an access token form the server and then retry the request with the new token.


import axios from "axios";

const instance = axios.create({
    baseURL: process.env.NEXT_PUBLIC_SERVER_URL,
    headers: {
        "Content-Type": "application/json",
    },
    withCredentials: true
});

instance.interceptors.response.use(response => response,
    async error => {
        const originalRequest = error.config;
        if (error.response.status === 403 && !originalRequest._retry) {
            originalRequest._retry = true;
            const res = await axios.post(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/user/refresh_token`, {}, { withCredentials: true });
            instance.defaults.headers.authorization = res.data.access_token;
            originalRequest.headers.authorization = res.data.access_token;
            return instance(originalRequest);
        }
        return Promise.reject(error);
    }
);

export default instance;

The user should not even realize that their access token has expired, their access will not be stalled or even cancelled, it should still go through.