Manejando la renovación de tokens en APIs con Axios

Manejando la renovación de tokens en APIs con Axios

En aplicaciones modernas, la autenticación mediante tokens (como JWT) es común. Pero estos tokens expiran y pueden causar múltiples errores 401 si no se gestionan bien sus renovaciones. Veamos cómo implementar una solución elegante para este problema usando Axios.

El Problema

Imagina una app con múltiples peticiones a una API que requiere un token. Cuando el token expira, la API devuelve 401. Si tenemos varias peticiones simultáneas y el token expira, obtendremos múltiples 401.

Una solución ingenua sería renovar el token para cada 401, pero esto sobrecargaría el servidor con múltiples solicitudes de renovación simultáneas.

La Solución

La solución es centralizar y coordinar el proceso de renovación, evitando múltiples solicitudes:

  1. Al detectar 401, en vez de renovar, ponemos la petición en una cola de "pendientes".
  2. Renovamos el token UNA VEZ.
  3. Cuando tenemos el token nuevo, reintentamos las peticiones pendientes.

El Código

Entonces utilizando Axios, la solución ser vería de la siguiente manera (por favor considera adaptar esta solución a tus necesidades, quizas la forma en que se obtiene el nuevo token varía y la respuesta del servidor para determinar el error 401 podría ser diferente, esto solo es un ejemplo de lo que deberías hacer):

// Variables para manejar la renovación del token.
let isRefreshing = false;
let refreshSubscribers = [];

// Función para suscribir una petición pendiente.
const subscribeTokenRefresh = (cb) => {
    refreshSubscribers.push(cb);
};

// Función para manejar las peticiones pendientes con un nuevo token.
const onRefreshed = (token) => {
    refreshSubscribers.forEach(cb => cb(token));
};

// Interceptor de Axios para peticiones.
axios.interceptors.response.use(
    response => response,
    error => {
        const { config, response: { status } } = error;
        const originalRequest = config;

        if (status === 401) {
            if (!isRefreshing) {
                isRefreshing = true;
                refreshToken().then(newToken => {
                    isRefreshing = false;
                    onRefreshed(newToken);
                    // no estaría de más que el nuevo token lo guardes en algún lugar de tu aplicación y reemplaces el token anterior
                });
            }

            const retryOriginalRequest = new Promise(resolve => {
                subscribeTokenRefresh(token => {
                    originalRequest.headers.Authorization = 'Bearer ' + token;
                    resolve(axios(originalRequest));
                });
            });
            return retryOriginalRequest;
        }
        return Promise.reject(error);
    }
);

Desglose del Código

  • isRefreshing: Una bandera para saber si ya estamos en el proceso de refrescar el token.

  • refreshSubscribers: Una lista que almacenará los callbacks de peticiones pendientes.

  • subscribeTokenRefresh: Añade una petición pendiente a la lista de refreshSubscribers.

  • onRefreshed: Una vez obtenido el nuevo token, procesa todas las peticiones pendientes con este token.

  • axios.interceptors.response.use: Este interceptor de Axios monitorea las respuestas. Si detecta un error 401, inicia el proceso de renovación del token. Las peticiones pendientes se almacenan y, una vez obtenido el nuevo token, se reintentan.

Conclusión

Esta solución es elegante porque centraliza el proceso de renovación del token. En lugar de tener múltiples peticiones intentando renovar el token simultáneamente, tenemos un único proceso que se encarga de ello. Las peticiones que fallaron se "pausan" temporalmente y luego se reintentan con el nuevo token.

Esta aproximación no solo es eficiente, sino que también mejora la experiencia del usuario, ya que las peticiones se reintentan automáticamente en caso de un fallo de autenticación. Además, evita sobrecargas innecesarias en el servidor de autenticación.

Happy coding! :D


Photo by Alexander Grey on Unsplash

Jack Fiallos

Jack Fiallos

Te gustó este artículo?