Oct 27th, 2021 · 2yr ago

Refresh Token com Axios Interceptors e fila de requisições

Written by Vinnicius Gomes · 3 min read

main image

Hoje eu vou te mostrar como eu resolvo o problema de refresh token nas minhas aplicações! 🤩

O problema:

A um tempo atrás eu estava com problemas de refreshToken na aplicação de um cliente, o que acontecia era que a página Dashboard chamava 3 endpoints diferentes, e quando dava erro 401 (Unauthorized), meu interceptor que criei dentro do Axios disparava 3 vezes a requisição de refreshToken ocasionando uma série de erros.

Então bora fazer isso funcionar?!

giphy

Primeiro passo, o que é Axios?

O Axios é um cliente HTTP, que funciona tanto no browser quanto em Node.Js. A biblioteca é basicamente uma API que sabe interagir tanto com XMLHttpRequest quanto com a interface HTTP do Node. Isso significa que o mesmo código utilizado para fazer requisições Ajax no browser também funciona no lado do servidor.

E agora que você já sabe o que é Axios, vamos falar sobre Interceptors

Os Interceptors são como o próprio nome diz, interceptadores. No Axios a gente consegue utiliza-los para executar alguma função antes que a Request e/ou Response seja iniciada.

Segue um exemplo:

// Adicionando um interceptor de Request
axios.interceptors.request.use(function (config) {
  // Faça algo antes que a solicitação seja enviada
  return config;
}, function (error) {
  // Faça algo com erro da solicitação
  return Promise.reject(error);
});
// Adicionando um interceptor de Response
axios.interceptors.response.use(function (response) {
  // Qualquer código de status que esteja dentro de 2xx fará com que essa função seja acionada
  // Faça algo com os dados de resposta
  return response;
}, function (error) {
  // Quaisquer códigos de status que estejam fora de 2xx fazem com que esta função seja acionada
  // Faça algo com erro de resposta
  return Promise.reject(error);
});

Esse exemplo você pode encontrar na própria documentação do Axios aravés desse link.

Pronto, agora chega de enrolação, e vamos para o que interessa!

giphy

⚠️ Alerta!

Um ponto importante, é que nesse post eu vou pular as etapas de criar uma aplicação React e fazer a instalação do Axios, então caso você não saiba como fazer algum desses passos só olhar a documentação do React ou do Axios.

Continuando…

1º Primeiro passo:

Vamos criar um arquivo chamado api.ts que vai ser responsável por armazenar toda a configuração do Axios que vai ficar dessa forma:

import axios from "axios";

export function api() {
  const api = axios.create({
    baseURL: "http://localhost:3333",
    headers: {
      Authorization: Bearer your_token,
    },
  });

  return api;
}

Nesse arquivo, criamos a configuração inicial para usar o Axios na aplicação.

2º Segundo passo:

Agora vamos criar o nosso interceptor que vai ser responsável por validar se houve um erro de status 401 para realizarmos o refreshToken.

import axios, { AxiosError } from "axios";
import { signOut } from "hooks/Auth";

// Variavel para informar se está acontecendo uma requisição de refresh token
let isRefreshing = false;
// Variavel para armazenar a fila de requisições que falharam por token expirado
let failedRequestQueue = [];

// Tipagem dos dados de response da api de autenticação
type AuthApiResponse = {
  token: string;
  refreshToken: string;
};

export function api() {
  // Cria as configurações iniciais do Axios
  const api = axios.create({
    baseURL: "http://localhost:3333",
    headers: {
      Authorization: Bearer your_token,
    },
  });

  // Cria um interceptor para interceptar todas as requisições que forem feitas
  api.interceptors.response.use(
    (response) => {
      // Se a requisição der sucesso, retorna a resposta
      return response;
    },
    (error: AxiosError) => {
      // Se a requisição der erro, verifica se o erro é de autenticação
      if (error.response.status === 401) {
        // Se o erro for de autenticação, verifica se o erro foi de token expirado
        if (error.response.data?.code === "token.expired") {
          // Recupera o refresh token do localStorage
          const refreshToken = localStorage.getItem("refreshToken");
          // Recupera toda a requisição que estava sendo feita e deu erro para ser refeita após o refresh token
          const originalConfig = error.config;

          // Verifica se já existe uma request de refreshToken acontecendo
          if (!isRefreshing) {
            // Se não existir, inicia a requisição de refreshToken
            isRefreshing = true;

            // Faz uma requisição de refreshToken
            api
              .post("/refresh", {
                refreshToken,
              })
              .then((response) => {
                // Recupera os dados do response e cria o newRefreshToken por que já está sendo utilizado a variável refreshToken
                const { token, refreshToken: newRefreshToken } =
                  response.data as AuthApiResponse;

                // Salva o token no localStorage
                localStorage.setItem("token", token);
                // Salva o refreshToken no localStorage
                localStorage.setItem("refreshToken", newRefreshToken);

                // Define novamente o header de autorização nas requisições
                api.defaults.headers["Authorization"] = Bearer your_token;

                // Faz todas as requisições que estavam na fila e falharam
                failedRequestQueue.forEach((request) =>
                  request.onSuccess(token)
                );
                // Limpa a fila de requisições que falharam
                failedRequestQueue = [];
              })
              .catch((err) => {
                // Retorna os erros que estão salvos na fila de requisições que falharam
                failedRequestQueue.forEach((request) => request.onFailure(err));
                // Limpa a fila de requisições que falharam
                failedRequestQueue = [];

                // Caso der erro desloga o usuário
                signOut();
              })
              .finally(() => {
                // Indica que a requisição de refreshToken acabou
                isRefreshing = false;
              });
          }

          // Usando a Promise no lugar do async await, para que a requisição seja feita após o refresh token
          return new Promise((resolve, reject) => {
            // Adiciona a requisição na fila de requisições que falharam com as informações necessárias para refazer a requisição novamente
            failedRequestQueue.push({
              // Se a requisição der sucesso, chama o onSuccess
              onSuccess: (token: string) => {
                // Adiciona o novo token gerado no refresh token no header de autorização
                originalConfig.headers["Authorization"] = Bearer your_token;

                // Faz a requisição novamente passando as informações originais da requisição que falhou
                resolve(api(originalConfig));
              },
              // Se a requisição der erro, chama o onFailure
              onFailure: (err: AxiosError) => {
                // Se não for possivel refazer a requisição, retorna o erro
                reject(err);
              },
            });
          });
        } else {
          // Caso der erro desloga o usuário
          signOut();
        }
      }

      // Se não cair em nenhum if retorna um error padrão
      return Promise.reject(error);
    }
  );

  return api;
}

Todo o código com o interceptor e a nossa fila de requisição fica assim, mas calma que eu vou te explicar o que está rolando!

  • Linha 11: Criamos uma variável para saber se já está acontecendo um refreshToken em caso de multiplas requisições que retornaram erro
  • Linha 13: Criamos uma variável para armazenar todas as requisições que deram erro, criando a nossa fila de requisições
  • Linha 31: Estamos criando o nosso Interceptor
  • Linha 32: Se a requisição der sucesso, apenas retornamos o response original
  • Linha 36: Todo erro que a requisição retornar cai nessa parte
  • Linha 38 – 40: Nessas linhas estamos apenas verificando se o erro que foi retornado pela API é um erro de autenticação
  • Linha 44: Recuperamos todo a request original para refazer quando o refreshToken for concluído
  • Linha 47: Verifica se não está acontecendo um fluxo de RefreshToken
  • Linha 51: Inicia a chamada para a API de refreshToken
  • Linha 70: Depois que a requisição de refreshToken for concluída, vamos realizar todas as outras requests que estavam na fila com o novo token gerado
  • Linha 74: Estamos voltando a fila para seu estado original
  • Linha 92: Quando já está acontecendo um refreshToken, nosso código vai cair nessa linha e vai recuperar e armazenar todas as requisições que falharam e vai armazenar na nossa variável de fila para ser executado após o sucesso do refreshToken
  • Linha 112: Caso der erro, deslogamos o usuário

Esse foi um breve resumo do que cada linha está fazendo, mas todo o código está comentado, para facilitar seu entendimento!

Pronto!

Agora toda vez que você tiver um ou mais erros de 401 (Unauthorized) na sua aplicação, automaticamente vai ser feito um refreshToken e vai ser chamado novamente as requisições que falharam sem precisar atualizar a tela!

Simples né?!

giphy

Bom, é isso, espero que tenha gostado!

React
Axios
Http Request
Refresh Token
Oauth

Thoughts about this article?

I'm all ears for feedback! Typos? Did something specific get your attention? Anything else? I'd love to hear! Drop me a note somewhere.

©2024 Vinnicius Gomes

Created in