Adjunte el encabezado de autorización para todas las solicitudes de axios

129

Tengo una aplicación react / redux que obtiene un token de un servidor api. Después de que el usuario se autentica, me gustaría hacer que todas las solicitudes de axios tengan ese token como un encabezado de autorización sin tener que adjuntarlo manualmente a cada solicitud en la acción. Soy bastante nuevo en reaccionar / redux y no estoy seguro del mejor enfoque y no encuentro ningún resultado de calidad en Google.

Aquí está mi configuración de redux:

// actions.js
import axios from 'axios';

export function loginUser(props) {
  const url = `https://api.mydomain.com/login/`;
  const { email, password } = props;
  const request = axios.post(url, { email, password });

  return {
    type: LOGIN_USER,
    payload: request
  };
}

export function fetchPages() {
  /* here is where I'd like the header to be attached automatically if the user
     has logged in */ 
  const request = axios.get(PAGES_URL);

  return {
    type: FETCH_PAGES,
    payload: request
  };
}

// reducers.js
const initialState = {
  isAuthenticated: false,
  token: null
};

export default (state = initialState, action) => {
  switch(action.type) {
    case LOGIN_USER:
      // here is where I believe I should be attaching the header to all axios requests.
      return {
        token: action.payload.data.key,
        isAuthenticated: true
      };
    case LOGOUT_USER:
      // i would remove the header from all axios requests here.
      return initialState;
    default:
      return state;
  }
}

Mi token se almacena en la tienda redux debajo state.session.token.

Estoy un poco perdido sobre cómo proceder. Intenté hacer una instancia de axios en un archivo en mi directorio raíz y actualizar / importar eso en lugar de desde node_modules, pero no adjunta el encabezado cuando cambia el estado. Cualquier comentario / idea es muy apreciado, gracias.

awwester
fuente
¿Dónde almacena el token de autorización después de recibir el token del servidor? ¿almacenamiento local?
Hardik Modha
en la tienda redux
session.token

Respuestas:

200

Hay varias formas de lograrlo. Aquí, he explicado los dos enfoques más comunes.

1. Puede usar interceptores axios para interceptar cualquier solicitud y agregar encabezados de autorización.

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    const token = store.getState().session.token;
    config.headers.Authorization =  token;

    return config;
});

2. En la documentación de axiospuede ver que hay un mecanismo disponible que le permite establecer un encabezado predeterminado que se enviará con cada solicitud que realice.

axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;

Entonces, en tu caso:

axios.defaults.headers.common['Authorization'] = store.getState().session.token;

Si lo desea, puede crear una función autoejecutable que establecerá el encabezado de autorización cuando el token esté presente en la tienda.

(function() {
     String token = store.getState().session.token;
     if (token) {
         axios.defaults.headers.common['Authorization'] = token;
     } else {
         axios.defaults.headers.common['Authorization'] = null;
         /*if setting null does not remove `Authorization` header then try     
           delete axios.defaults.headers.common['Authorization'];
         */
     }
})();

Ahora ya no necesita adjuntar el token manualmente a cada solicitud. Puede colocar la función anterior en el archivo que se garantiza que se ejecutará cada vez ( por ejemplo: Archivo que contiene las rutas).

Espero eso ayude :)

Hardik Modha
fuente
1
ya estoy usando redux-persist pero echará un vistazo al middleware para adjuntar el token en el encabezado, ¡gracias!
awwester
1
@awwester No necesita middleware para adjuntar el token en el encabezado. Adjuntar token en el encabezado es axios.defaults.header.common[Auth_Token] = tokentan simple como eso.
Hardik Modha
4
@HardikModha Tengo curiosidad por saber cómo se puede hacer esto con Fetch API.
Rowland
@ Rowland Creo que necesitará escribir un contenedor sobre la API de recuperación para lograr lo mismo. La respuesta detallada a esa pregunta está fuera del alcance de la pregunta formulada por el PO. Puede hacer otra pregunta :)
Hardik Modha
2
Hola @HardikModha. Si utilizo los encabezados predeterminados para el token configurado cuando quiero renovar el token, no se puede volver a configurar en el encabezado. ¿es correcto? Entonces tengo que usar los interceptores.
principiante
50

Si usa la versión "axios": "^ 0.17.1", puede hacer lo siguiente:

Crear instancia de axios:

// Default config options
  const defaultOptions = {
    baseURL: <CHANGE-TO-URL>,
    headers: {
      'Content-Type': 'application/json',
    },
  };

  // Create instance
  let instance = axios.create(defaultOptions);

  // Set the AUTH token for any request
  instance.interceptors.request.use(function (config) {
    const token = localStorage.getItem('token');
    config.headers.Authorization =  token ? `Bearer ${token}` : '';
    return config;
  });

Luego, para cualquier solicitud, el token se seleccionará de localStorage y se agregará a los encabezados de la solicitud.

Estoy usando la misma instancia en toda la aplicación con este código:

import axios from 'axios';

const fetchClient = () => {
  const defaultOptions = {
    baseURL: process.env.REACT_APP_API_PATH,
    method: 'get',
    headers: {
      'Content-Type': 'application/json',
    },
  };

  // Create instance
  let instance = axios.create(defaultOptions);

  // Set the AUTH token for any request
  instance.interceptors.request.use(function (config) {
    const token = localStorage.getItem('token');
    config.headers.Authorization =  token ? `Bearer ${token}` : '';
    return config;
  });

  return instance;
};

export default fetchClient();

Buena suerte.

llioor
fuente
@ NguyễnPhúc Con mucho gusto, el punto es usar "interceptores" de axios
llioor
Esta es la mejor respuesta ... ¡inicializar el token en los interceptores para cada solicitud! . Gracias
james ace
45

La mejor solución para mí es crear un servicio al cliente que instanciará con su token y lo usará para envolver axios.

import axios from 'axios';

const client = (token = null) => {
    const defaultOptions = {
        headers: {
            Authorization: token ? `Token ${token}` : '',
        },
    };

    return {
        get: (url, options = {}) => axios.get(url, { ...defaultOptions, ...options }),
        post: (url, data, options = {}) => axios.post(url, data, { ...defaultOptions, ...options }),
        put: (url, data, options = {}) => axios.put(url, data, { ...defaultOptions, ...options }),
        delete: (url, options = {}) => axios.delete(url, { ...defaultOptions, ...options }),
    };
};

const request = client('MY SECRET TOKEN');

request.get(PAGES_URL);

En este cliente, también puede recuperar el token del localStorage / cookie, como desee.

Kmaschta
fuente
1
¿Qué sucede si desea realizar request.get () con encabezados de "tipo de aplicación"? Con su enfoque, los encabezados de defaultOptions serán reemplazados por los encabezados de la solicitud. ¿Estoy en lo cierto? Gracias.
nipuro
9

Del mismo modo, tenemos una función para configurar o eliminar el token de llamadas como esta:

import axios from 'axios';

export default function setAuthToken(token) {
  axios.defaults.headers.common['Authorization'] = '';
  delete axios.defaults.headers.common['Authorization'];

  if (token) {
    axios.defaults.headers.common['Authorization'] = `${token}`;
  }
}

Siempre limpiamos el token existente en la inicialización, luego establecemos el recibido.

elQueFaltaba
fuente
5

Si desea llamar a otras rutas de API en el futuro y mantener su token en la tienda, intente usar el middleware redux .

El middleware podría escuchar la acción de una API y enviar solicitudes de API a través de axios en consecuencia.

Aquí hay un ejemplo muy básico:

acciones / api.js

export const CALL_API = 'CALL_API';

function onSuccess(payload) {
  return {
    type: 'SUCCESS',
    payload
  };
}

function onError(payload) {
  return {
    type: 'ERROR',
    payload,
    error: true
  };
}

export function apiLogin(credentials) {
  return {
    onSuccess,
    onError,
    type: CALL_API,
    params: { ...credentials },
    method: 'post',
    url: 'login'
  };
}

middleware / api.js

import axios from 'axios';
import { CALL_API } from '../actions/api';

export default ({ getState, dispatch }) => next => async action => {
  // Ignore anything that's not calling the api
  if (action.type !== CALL_API) {
    return next(action);
  }

  // Grab the token from state
  const { token } = getState().session;

  // Format the request and attach the token.
  const { method, onSuccess, onError, params, url } = action;

  const defaultOptions = {
    headers: {
      Authorization: token ? `Token ${token}` : '',
    }
  };

  const options = {
    ...defaultOptions,
    ...params
  };

  try {
    const response = await axios[method](url, options);
    dispatch(onSuccess(response.data));
  } catch (error) {
    dispatch(onError(error.data));
  }

  return next(action);
};
Pablo. si
fuente
3

A veces, se presenta un caso en el que algunas de las solicitudes realizadas con axios apuntan a puntos finales que no aceptan encabezados de autorización. Por lo tanto, la forma alternativa de configurar el encabezado de autorización solo en el dominio permitido es como en el ejemplo siguiente. Coloque la siguiente función en cualquier archivo que se ejecute cada vez que se ejecute la aplicación React, como en el archivo de rutas.

export default () => {
    axios.interceptors.request.use(function (requestConfig) {
        if (requestConfig.url.indexOf(<ALLOWED_DOMAIN>) > -1) {
            const token = localStorage.token;
            requestConfig.headers['Authorization'] = `Bearer ${token}`;
        }

        return requestConfig;
    }, function (error) {
        return Promise.reject(error);
    });

}
Karolis Ramanauskas
fuente
0

Intenta hacer una nueva instancia como hice a continuación

var common_axios = axios.create({
    baseURL: 'https://sample.com'
});

// Set default headers to common_axios ( as Instance )
common_axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// Check your Header
console.log(common_axios.defaults.headers);

Cómo usarlo

common_axios.get(url).......
common_axios.post(url).......
ugali suave
fuente
0
export const authHandler = (config) => {
  const authRegex = /^\/apiregex/;

  if (!authRegex.test(config.url)) {
    return store.fetchToken().then((token) => {
      Object.assign(config.headers.common, { Authorization: `Bearer ${token}` });
      return Promise.resolve(config);
    });
  }
  return Promise.resolve(config);
};

axios.interceptors.request.use(authHandler);

Me encontré con algunos errores al intentar implementar algo similar y, en base a estas respuestas, esto es lo que se me ocurrió. Los problemas que estaba experimentando eran:

  1. Si usa axios para la solicitud para obtener un token en su tienda, debe detectar la ruta antes de agregar el encabezado. Si no lo hace, intentará agregar el encabezado a esa llamada también y entrará en un problema de ruta circular. La inversa de agregar expresiones regulares para detectar las otras llamadas también funcionaría
  2. Si la tienda está devolviendo una promesa, debe devolver la llamada a la tienda para resolver la promesa en la función authHandler. La funcionalidad Async / Await haría esto más fácil / más obvio
  3. Si la llamada para el token de autenticación falla o es la llamada para obtener el token, aún desea resolver una promesa con la configuración
Bhetzie
fuente
0

El punto es establecer el token en los interceptores para cada solicitud

import axios from "axios";
    
const httpClient = axios.create({
    baseURL: "http://youradress",
    // baseURL: process.env.APP_API_BASE_URL,
});

httpClient.interceptors.request.use(function (config) {
    const token = localStorage.getItem('token');
    config.headers.Authorization =  token ? `Bearer ${token}` : '';
    return config;
});
james ace
fuente