302

I have a react/redux application that fetches a token from an api server. After the user authenticates I'd like to make all axios requests have that token as an Authorization header without having to manually attach it to every request in the action. I'm fairly new to react/redux and am not sure on the best approach and am not finding any quality hits on google.

Here is my redux setup:

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

My token is stored in redux store under state.session.token.

I'm a bit lost on how to proceed. I've tried making an axios instance in a file in my root directory and update/import that instead of from node_modules but it's not attaching the header when the state changes. Any feedback/ideas are much appreciated, thanks.

awwester
  • 9,623
  • 13
  • 45
  • 72

9 Answers9

460

There are multiple ways to achieve this. Here, I have explained the two most common approaches.

1. You can use axios interceptors to intercept any requests and add authorization headers.

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

2. From the documentation of axios you can see there is a mechanism available which allows you to set default header which will be sent with every request you make.

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

So in your case:

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

If you want, you can create a self-executable function which will set authorization header itself when the token is present in the store.

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

Now you no longer need to attach token manually to every request. You can place the above function in the file which is guaranteed to be executed every time (e.g: File which contains the routes).

starball
  • 20,030
  • 7
  • 43
  • 238
Hardik Modha
  • 12,098
  • 3
  • 36
  • 40
  • 1
    already using redux-persist but will take a look at middleware to attach the token in header, thanks! – awwester Mar 27 '17 at 23:34
  • 4
    @awwester You don't need middleware to attach the token in the header. Attaching token in header is `axios.defaults.header.common[Auth_Token] = token` as simple as that. – Hardik Modha Mar 28 '17 at 01:32
  • 4
    @HardikModha I'm curious how one might be able to do this with Fetch API. – Rowland Aug 06 '17 at 18:09
  • @Rowland I believe, You'll need to write a wrapper over fetch API to achieve the same. The detailed answer to that question is out of the scope of the question asked by the OP. You can ask another question :) – Hardik Modha Mar 30 '19 at 08:08
  • 2
    Hi @HardikModha. If I use the default headers for the set token when I want to renew the token, it's can not set again into the header. is it correct? So i have to use the interceptors. – beginerdeveloper Nov 06 '19 at 10:22
  • @HardikModha, may I ask you to have a look at an `axios` related question here : https://stackoverflow.com/questions/59470085/vue-js-validation-fails-for-file-upload-in-axios-when-multipart-form-data-used ? – Istiaque Ahmed Dec 24 '19 at 14:29
  • You might need to add `config.headers.Authorization = Bearer ${token}'`. – Yuri Cardoso Dec 30 '19 at 22:55
  • @Rowland You might be interested in https://github.com/developit/redaxios. It's a wrapper on the fetch API. API is similar to Axios. – Hardik Modha Apr 18 '20 at 05:16
  • @HardikModha How to set this line using Hooks? `axios.defaults.headers.common['Authorization'] = store.getState().session.token;` – Faizan Mubasher Jun 30 '20 at 05:12
  • 3
    A minor gotcha: You will have to set default headers for each instance of Axios in your application separately if you are following second method. This took me a while to figure out. – Waseem Jul 22 '20 at 08:18
  • @HardikModha what is `store.getState()`? is this your redux store? you don't store token in a cookie? – asus Aug 08 '20 at 00:50
  • 1
    For some reason 2nd one doesn't work :( But first one does. Thanks for the solution btw :) – sheikhsalman08 Oct 01 '20 at 14:41
  • I'm curious. When making API calls (say with axios), how do you typically handle calling public vs private endpoints (with/without jwt auth headers)? 2 separate axios instances, one with interceptors that handle the token lifecycle and one without? – ChambreNoire Dec 24 '21 at 17:19
  • 1
    To avoid doing this for every instance of axios, you can do it in the login logic. – elonaire Dec 24 '21 at 18:49
119

Create instance of 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;
  });

Then for any request the token will be select from localStorage and will be added to the request headers.

I'm using the same instance all over the app with this code:

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();
starball
  • 20,030
  • 7
  • 43
  • 238
llioor
  • 5,804
  • 4
  • 36
  • 44
51

The best solution to me is to create a client service that you'll instantiate with your token an use it to wrap 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);

In this client, you can also retrieve the token from the localStorage / cookie, as you want.

Hardik Modha
  • 12,098
  • 3
  • 36
  • 40
Kmaschta
  • 2,369
  • 1
  • 18
  • 36
  • 2
    What if you want to make the request.get() with "application-type" headers. With your approach the headers from defaultOptions will be overwitten by headers from request. I'm right? Thank you. – nipuro Nov 15 '19 at 21:07
14

Similarly, we have a function to set or delete the token from calls like this:

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

We always clean the existing token at initialization, then establish the received one.

elQueFaltaba
  • 618
  • 1
  • 9
  • 17
13

The point is to set the token on the interceptors for each request

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 Ikubi
  • 2,552
  • 25
  • 18
5

If you want to call other api routes in the future and keep your token in the store then try using redux middleware.

The middleware could listen for the an api action and dispatch api requests through axios accordingly.

Here is a very basic example:

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);
};
Jeffrey Rajan
  • 4,391
  • 4
  • 27
  • 37
Paul. B
  • 1,328
  • 1
  • 13
  • 22
4

Sometimes you get a case where some of the requests made with axios are pointed to endpoints that do not accept authorization headers. Thus, alternative way to set authorization header only on allowed domain is as in the example below. Place the following function in any file that gets executed each time React application runs such as in routes file.

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
  • 1,045
  • 1
  • 9
  • 14
3

Try to make new instance like i did below

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

How to Use it

common_axios.get(url).......
common_axios.post(url).......
ugali soft
  • 2,719
  • 28
  • 25
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);

Ran into some gotchas when trying to implement something similar and based on these answers this is what I came up with. The problems I was experiencing were:

  1. If using axios for the request to get a token in your store, you need to detect the path before adding the header. If you don't, it will try to add the header to that call as well and get into a circular path issue. The inverse of adding regex to detect the other calls would also work
  2. If the store is returning a promise, you need to return the call to the store to resolve the promise in the authHandler function. Async/Await functionality would make this easier/more obvious
  3. If the call for the auth token fails or is the call to get the token, you still want to resolve a promise with the config
Bhetzie
  • 2,852
  • 10
  • 32
  • 43