Dado que quiero configurar los interceptores Axios con React Context, la única solución que parece viable es crear un componente Interceptor para usar el gancho useContext para acceder al estado y envío de Contexto.
El problema es que esto crea un cierre y devuelve datos antiguos al interceptor cuando se lo llama.
Estoy usando la autenticación JWT usando React/Node y estoy almacenando tokens de acceso usando la API de contexto.
Así es como se ve mi componente Interceptor en este momento:
import React, { useEffect, useContext } from 'react'; import { Context } from '../../components/Store/Store'; import { useHistory } from 'react-router-dom'; import axios from 'axios'; const ax = axios.create(); const Interceptor = ({ children }) => { const [store, dispatch] = useContext(Context); const history = useHistory(); const getRefreshToken = async () => { try { if (!store.user.token) { dispatch({ type: 'setMain', loading: false, error: false, auth: store.main.auth, brand: store.main.brand, theme: store.main.theme, }); const { data } = await axios.post('/api/auth/refresh_token', { headers: { credentials: 'include', }, }); if (data.user) { dispatch({ type: 'setStore', loading: false, error: false, auth: store.main.auth, brand: store.main.brand, theme: store.main.theme, authenticated: true, token: data.accessToken, id: data.user.id, name: data.user.name, email: data.user.email, photo: data.user.photo, stripeId: data.user.stripeId, country: data.user.country, messages: { items: [], count: data.user.messages, }, notifications: store.user.notifications.items.length !== data.user.notifications ? { ...store.user.notifications, items: [], count: data.user.notifications, hasMore: true, cursor: 0, ceiling: 10, } : { ...store.user.notifications, count: data.user.notifications, }, saved: data.user.saved.reduce(function (object, item) { object[item] = true; return object; }, {}), cart: { items: data.user.cart.reduce(function (object, item) { object[item.artwork] = true; return object; }, {}), count: Object.keys(data.user.cart).length, }, }); } else { dispatch({ type: 'setMain', loading: false, error: false, auth: store.main.auth, brand: store.main.brand, theme: store.main.theme, }); } } } catch (err) { dispatch({ type: 'setMain', loading: false, error: true, auth: store.main.auth, brand: store.main.brand, theme: store.main.theme, }); } }; const interceptTraffic = () => { ax.interceptors.request.use( (request) => { request.headers.Authorization = store.user.token ? `Bearer ${store.user.token}` : ''; return request; }, (error) => { return Promise.reject(error); } ); ax.interceptors.response.use( (response) => { return response; }, async (error) => { console.log(error); if (error.response.status !== 401) { return new Promise((resolve, reject) => { reject(error); }); } if ( error.config.url === '/api/auth/refresh_token' || error.response.message === 'Forbidden' ) { const { data } = await ax.post('/api/auth/logout', { headers: { credentials: 'include', }, }); dispatch({ type: 'resetUser', }); history.push('/login'); return new Promise((resolve, reject) => { reject(error); }); } const { data } = await axios.post(`/api/auth/refresh_token`, { headers: { credentials: 'include', }, }); dispatch({ type: 'updateUser', token: data.accessToken, email: data.user.email, photo: data.user.photo, stripeId: data.user.stripeId, country: data.user.country, messages: { items: [], count: data.user.messages }, notifications: store.user.notifications.items.length !== data.user.notifications ? { ...store.user.notifications, items: [], count: data.user.notifications, hasMore: true, cursor: 0, ceiling: 10, } : { ...store.user.notifications, count: data.user.notifications, }, saved: data.user.saved, cart: { items: {}, count: data.user.cart }, }); const config = error.config; config.headers['Authorization'] = `Bearer ${data.accessToken}`; return new Promise((resolve, reject) => { axios .request(config) .then((response) => { resolve(response); }) .catch((error) => { reject(error); }); }); } ); }; useEffect(() => { getRefreshToken(); if (!store.main.loading) interceptTraffic(); }, []); return store.main.loading ? 'Loading...' : children; } export { ax }; export default Interceptor; Se llama a la función getRefreshToken cada vez que un usuario actualiza el sitio web para recuperar un token de acceso si hay un token de actualización en la cookie.
La función interceptTraffic es donde persiste el problema. Consiste en un interceptor de solicitudes que agrega un encabezado con el token de acceso a cada solicitud y un interceptor de respuesta que se usa para manejar el vencimiento del token de acceso para obtener uno nuevo usando un token de actualización.
Notará que estoy exportando ax (una instancia de Axios donde agregué interceptores) pero cuando se llama fuera de este componente, hace referencia a datos antiguos de la tienda debido al cierre.
Obviamente, esta no es una buena solución, pero es por eso que necesito ayuda para organizar los interceptores sin dejar de poder acceder a los datos de contexto.
Tenga en cuenta que creé este componente como un envoltorio, ya que representa los elementos secundarios que se le proporcionan, que es el componente principal de la aplicación.
Cualquier ayuda es apreciada, gracias.
Es una práctica común almacenar el JWT en localStorage con
localStorage.setItem('token', 'your_jwt_eykdfjkdf...');al iniciar sesión o actualizar la página, y cree un módulo que exporte una instancia de Axios con el token adjunto. Obtendremos el token de localStorage
custom-axios.js
import axios from 'axios'; // axios instance for making requests const axiosInstance = axios.create(); // request interceptor for adding token axiosInstance.interceptors.request.use((config) => { // add token to request headers config.headers['Authorization'] = localStorage.getItem('token'); return config; }); export default axiosInstance;Y luego, simplemente importe la instancia de Axios que acabamos de crear y realice solicitudes.
import axios from './custom-axios'; axios.get('/url'); axios.post('/url', { message: 'hello' });Si tiene su JWT almacenado en el estado o puede obtener un token nuevo del estado, cree un módulo que exporte una función que tome el token como argumento y devuelva una instancia de axios con el token adjunto de esta manera:
custom-axios.js
import axios from 'axios'; const customAxios = (token) => { // axios instance for making requests const axiosInstance = axios.create(); // request interceptor for adding token axiosInstance.interceptors.request.use((config) => { // add token to request headers config.headers['Authorization'] = token; return config; }); return axiosInstance; }; export default customAxios;Y luego importe la función que acabamos de crear, tome el token del estado y haga solicitudes:
import axios from './custom-axios'; // logic to get token from state (it may vary from your approach but the idea is same) const token = useSelector(token => token); axios(token).get('/url'); axios(token).post('/url', { message: 'hello' });Tengo una plantilla que funciona en un sistema con millones de accesos todos los días.
Esto resolvió mis problemas con el token de actualización y volvió a intentar la solicitud sin fallar
Primero tengo un "api.js" con axios, configuraciones, direcciones, encabezados. En este archivo hay dos métodos, uno con autenticación y otro sin ella. En este mismo archivo configuré mi interceptor:
import axios from "axios"; import { ResetTokenAndReattemptRequest } from "domain/auth/AuthService"; export const api = axios.create({ baseURL: process.env.REACT_APP_API_URL, headers: { "Content-Type": "application/json", }, }); export const apiSecure = axios.create({ baseURL: process.env.REACT_APP_API_URL, headers: { Authorization: "Bearer " + localStorage.getItem("Token"), "Content-Type": "application/json", }, export default api; apiSecure.interceptors.response.use( function (response) { return response; }, function (error) { const access_token = localStorage.getItem("Token"); if (error.response.status === 401 && access_token) { return ResetTokenAndReattemptRequest(error); } else { console.error(error); } return Promise.reject(error); } );Luego, el método ResetTokenAndReattemptRequest. Lo coloqué en otro archivo, pero puedes colocarlo donde quieras:
import api from "../api"; import axios from "axios"; let isAlreadyFetchingAccessToken = false; let subscribers = []; export async function ResetTokenAndReattemptRequest(error) { try { const { response: errorResponse } = error; const retryOriginalRequest = new Promise((resolve) => { addSubscriber((access_token) => { errorResponse.config.headers.Authorization = "Bearer " + access_token; resolve(axios(errorResponse.config)); }); }); if (!isAlreadyFetchingAccessToken) { isAlreadyFetchingAccessToken = true; await api .post("/Auth/refresh", { Token: localStorage.getItem("RefreshToken"), LoginProvider: "Web", }) .then(function (response) { localStorage.setItem("Token", response.data.accessToken); localStorage.setItem("RefreshToken", response.data.refreshToken); localStorage.setItem("ExpiresAt", response.data.expiresAt); }) .catch(function (error) { return Promise.reject(error); }); isAlreadyFetchingAccessToken = false; onAccessTokenFetched(localStorage.getItem("Token")); } return retryOriginalRequest; } catch (err) { return Promise.reject(err); } } function onAccessTokenFetched(access_token) { subscribers.forEach((callback) => callback(access_token)); subscribers = []; } function addSubscriber(callback) { subscribers.push(callback); }