为所有 axios 请求添加授权头信息

309
我有一个使用React/Redux编写的应用程序,从API服务器获取token。用户验证后,我想在所有axios请求中自动添加该token作为Authorization标头,而不必在每个操作中手动附加它。我对React/Redux比较新,不确定最佳方法,并且在Google上没有找到什么质量高的信息。
以下是我的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;
  }
}

我的令牌存储在redux store下的state.session.token中。

我有点迷失在如何继续下去。我尝试在根目录的文件中创建一个axios实例,并更新/导入它而不是从node_modules中导入,但是当状态改变时它没有附加头文件。非常感谢任何反馈/想法。


在从服务器接收到授权令牌后,您将其存储在哪里?localStorage? - Hardik Modha
在 Redux 存储中的 session.token。 - awwester
我之前是这样做的,但是添加一个非标准的头部比如授权,需要使用预检 OPTIONS 请求,这可能会给应用程序增加很多延迟。现在我正在尝试将其附加到 URL,并在可能的情况下继续使用 HTTP 的“简单”方法。 - undefined
9个回答

466

有多种方法可以实现这一点。在这里,我已经解释了两种最常见的方法。

1. 您可以使用axios拦截器拦截任何请求并添加授权标头。

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

2. 从axios文档中可以看到,有一种机制可用于设置默认头部信息,这样每次发出的请求都会带上这些头部信息。

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

那么在您的情况下:

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

如果你愿意,你可以创建一个自执行函数,在令牌存在于存储中时自动设置授权标头。

(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'];
         */
     }
})();

现在您不再需要手动将令牌附加到每个请求中。您可以将上述函数放置在保证每次执行的文件中(例如:包含路由的文件)。


4
您不需要中间件来将令牌附加到标头中。将令牌附加到标头中的方法非常简单,只需使用以下代码:axios.defaults.header.common[Auth_Token] = token - Hardik Modha
4
@HardikModha 我很好奇如何使用Fetch API来实现这个。 - Rowland
2
嗨@HardikModha。如果我在想要重新生成令牌时使用默认标头来设置令牌,那么它就无法再次设置到标头中了,这是正确的吗?所以我必须使用拦截器。 - beginerdeveloper
3
一个小细节:如果你采用第二种方法,你需要为应用程序中每个 Axios 实例单独设置默认头部信息。这让我花费了一些时间去理解。 - Waseem
1
为了避免针对每个axios实例都这样做,您可以在登录逻辑中执行它。 - elonaire
显示剩余9条评论

121
创建 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;
  });

然后对于任何请求,令牌将从localStorage中选择,并将添加到请求头中。

我在整个应用程序中使用相同的实例,使用以下代码:

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();

@NguyễnPhúc 很高兴,使用axios的“拦截器”是关键。 - llioor
1
这是最好的答案...为每个请求在拦截器上初始化令牌!谢谢。 - James Ikubi
你永远不应该将令牌存储在localStorage中。 - Amund
2
为什么?@Amund,如果关闭和打开应用程序,存储在哪里? - famfamfam

52

对我来说最好的解决方案是创建一个客户端服务,您将使用您的令牌实例化它并使用它来包装 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);
在此客户端中,您还可以从localStorage / cookie中检索令牌,按照您的意愿进行操作。

2
如果您想使用“应用程序类型”标头进行request.get()请求,那该怎么办呢?使用您的方法,defaultOptions中的标头将被request中的标头覆盖。我的理解正确吗?谢谢。 - nipuro

14

同样地,我们有一个函数可以设置或删除令牌,像这样的调用:

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}`;
  }
}

我们总是在初始化时清除现有的令牌,然后建立接收到的令牌。


13

重点是为每个请求设置拦截器上的令牌

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;
});

5
如果您想在将来调用其他API路由并在存储中保留令牌,则可以尝试使用redux中间件
中间件可以侦听API操作并相应地通过axios发送API请求。
以下是一个非常基本的示例:

actions/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);
};

4
有时候,使用axios发出的请求指向的端点不接受授权头。因此,将授权头仅设置在允许的域上的替代方法如下所示。将以下函数放置在任何文件中,该文件在每次运行React应用程序时都会执行,例如在路由文件中。
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);
    });

}

3
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);

我遇到了一些问题,当我尝试实现类似的东西并基于这些答案时,这就是我想出来的。我遇到的问题是:
  1. 如果在存储中使用axios请求获取令牌,则需要在添加标头之前检测路径。如果不这样做,它将尝试将标头添加到该调用中,并陷入循环路径问题。添加正则表达式以检测其他调用的反向操作也可以起作用。
  2. 如果存储返回一个promise,则需要在authHandler函数中返回对存储的调用以解决promise。使用Async / Await功能将使此过程更加容易/明显。
  3. 如果获取身份验证令牌的调用失败或是获取令牌的调用,则仍要使用config解析promise。

我遇到了完全相同的问题,很高兴找到了你的答案。虽然这很有道理。 - wintercounter

3

尝试像我下面所做的那样创建新实例

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);

如何使用它

common_axios.get(url).......
common_axios.post(url).......

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接