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?!

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:
1// Adicionando um interceptor de Request
2axios.interceptors.request.use(function (config) {
3 // Faça algo antes que a solicitação seja enviada
4 return config;
5}, function (error) {
6 // Faça algo com erro da solicitação
7 return Promise.reject(error);
8});
9// Adicionando um interceptor de Response
10axios.interceptors.response.use(function (response) {
11 // Qualquer código de status que esteja dentro de 2xx fará com que essa função seja acionada
12 // Faça algo com os dados de resposta
13 return response;
14}, function (error) {
15 // Quaisquer códigos de status que estejam fora de 2xx fazem com que esta função seja acionada
16 // Faça algo com erro de resposta
17 return Promise.reject(error);
18});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!

⚠️ 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:
1import axios from "axios";
2
3export function api() {
4 const api = axios.create({
5 baseURL: "http://localhost:3333",
6 headers: {
7 Authorization: Bearer your_token,
8 },
9 });
10
11 return api;
12}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.
1import axios, { AxiosError } from "axios";
2import { signOut } from "hooks/Auth";
3
4// Variavel para informar se está acontecendo uma requisição de refresh token
5let isRefreshing = false;
6// Variavel para armazenar a fila de requisições que falharam por token expirado
7let failedRequestQueue = [];
8
9// Tipagem dos dados de response da api de autenticação
10type AuthApiResponse = {
11 token: string;
12 refreshToken: string;
13};
14
15export function api() {
16 // Cria as configurações iniciais do Axios
17 const api = axios.create({
18 baseURL: "http://localhost:3333",
19 headers: {
20 Authorization: Bearer your_token,
21 },
22 });
23
24 // Cria um interceptor para interceptar todas as requisições que forem feitas
25 api.interceptors.response.use(
26 (response) => {
27 // Se a requisição der sucesso, retorna a resposta
28 return response;
29 },
30 (error: AxiosError) => {
31 // Se a requisição der erro, verifica se o erro é de autenticação
32 if (error.response.status === 401) {
33 // Se o erro for de autenticação, verifica se o erro foi de token expirado
34 if (error.response.data?.code === "token.expired") {
35 // Recupera o refresh token do localStorage
36 const refreshToken = localStorage.getItem("refreshToken");
37 // Recupera toda a requisição que estava sendo feita e deu erro para ser refeita após o refresh token
38 const originalConfig = error.config;
39
40 // Verifica se já existe uma request de refreshToken acontecendo
41 if (!isRefreshing) {
42 // Se não existir, inicia a requisição de refreshToken
43 isRefreshing = true;
44
45 // Faz uma requisição de refreshToken
46 api
47 .post("/refresh", {
48 refreshToken,
49 })
50 .then((response) => {
51 // Recupera os dados do response e cria o newRefreshToken por que já está sendo utilizado a variável refreshToken
52 const { token, refreshToken: newRefreshToken } =
53 response.data as AuthApiResponse;
54
55 // Salva o token no localStorage
56 localStorage.setItem("token", token);
57 // Salva o refreshToken no localStorage
58 localStorage.setItem("refreshToken", newRefreshToken);
59
60 // Define novamente o header de autorização nas requisições
61 api.defaults.headers["Authorization"] = Bearer your_token;
62
63 // Faz todas as requisições que estavam na fila e falharam
64 failedRequestQueue.forEach((request) =>
65 request.onSuccess(token)
66 );
67 // Limpa a fila de requisições que falharam
68 failedRequestQueue = [];
69 })
70 .catch((err) => {
71 // Retorna os erros que estão salvos na fila de requisições que falharam
72 failedRequestQueue.forEach((request) => request.onFailure(err));
73 // Limpa a fila de requisições que falharam
74 failedRequestQueue = [];
75
76 // Caso der erro desloga o usuário
77 signOut();
78 })
79 .finally(() => {
80 // Indica que a requisição de refreshToken acabou
81 isRefreshing = false;
82 });
83 }
84
85 // Usando a Promise no lugar do async await, para que a requisição seja feita após o refresh token
86 return new Promise((resolve, reject) => {
87 // Adiciona a requisição na fila de requisições que falharam com as informações necessárias para refazer a requisição novamente
88 failedRequestQueue.push({
89 // Se a requisição der sucesso, chama o onSuccess
90 onSuccess: (token: string) => {
91 // Adiciona o novo token gerado no refresh token no header de autorização
92 originalConfig.headers["Authorization"] = Bearer your_token;
93
94 // Faz a requisição novamente passando as informações originais da requisição que falhou
95 resolve(api(originalConfig));
96 },
97 // Se a requisição der erro, chama o onFailure
98 onFailure: (err: AxiosError) => {
99 // Se não for possivel refazer a requisição, retorna o erro
100 reject(err);
101 },
102 });
103 });
104 } else {
105 // Caso der erro desloga o usuário
106 signOut();
107 }
108 }
109
110 // Se não cair em nenhum if retorna um error padrão
111 return Promise.reject(error);
112 }
113 );
114
115 return api;
116}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é?!

Bom, é isso, espero que tenha gostado!
