Where to add/remove interceptors while different components are calling apis using an axiosinstance

Viewed 230

I have axios instance initialized at the start of application. Under Login.js I am able to get the token and want to add it to the header using interceptors, for most subsequent api calls e.g. when using under AddSampleTable.js . (A few of them will require to go without Authorization header too e.g. ForgotPassword.js)

Currently I have to do this for every single api call in each component. My current code is as follows

axios.js

import axios from 'axios';

 const baseURL = process.env.REACT_APP_BASE_URL;

 let headers = {};

//this never executes since we havent logged in yet
  
//if(localStorage.token) {
  //headers.Authorization = `Bearer ${localStorage.token}`; 
//}
const token = localStorage.getItem('token');

const axiosInstance = axios.create({

    baseURL: baseURL,
    headers: {'Authorization': token? `Bearer ${token}`: null},
});    
  
export default axiosInstance;

Login.js

 import axiosInstance from '../../helpers/axios';

  const printValues = e =>{
  e.preventDefault();
 axiosInstance.post('/auth', data)
 .then(res =>{
  console.log("writing token");
  dispatch(jwtTokenRecieved(res.data.token));
  localStorage.setItem('token',res.data.token);

  const config = {
    headers:{
      Authorization:'Bearer '+res.data.token
    }
  }
  axiosInstance.get('/User/GetUserByID/0', config)
.then(res =>{
    dispatch(isLoggedUser(res.data));
  })
  .catch(err =>{
    console.log(err);
  })

AddSampleTable.js

This is where I want to use the instance and token should be present by default but currently I am extracting for each api call from localstorage

import axiosInstance from '../../helpers/axios';
export default function AddSamplesTable(){
const jwtToken = useSelector(state => state?.token?.data || '');

const retrieveSampleData = () =>{

const config = {
  headers:{
    Authorization:'Bearer '+ jwtToken,
    'Content-Type': 'application/json'
  }
}
axiosInstance.get('/batch/'+CoCData.id, config) 
    .then(function (response) {
      setSamples(response.data.samples);
    })
    .catch(function (error) {
      console.log(error);
    });
} 


}

Note I am also using reducers and actions to set token into the localStorage as you see in (in addition to saving it via setItem)

dispatch(jwtTokenRecieved(res.data.token));

Update: I have tried using interceptors in axios.js after create function as follows

axiosInstance.interceptors.request.use(
config => {
console.log(config)
  const token = JSON.parse(localStorage.getItem('token'));
  config.headers.Authorization =  token ? `Bearer ${token}`: null;

  return config;
    }
);

but when a new user logs in, the existing token value does not get updated with the new token and I get

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'status')

3 Answers

An opinionated answer here, but I would argue in favour of splitting your React app's code into two areas:

VIEWS

By all means use the React style, where each source file contains only functions, and follow React best practice.

GENERAL CODE / API CALLS

Classes often work better here, in terms of encapsulating code and data together in a single instance. Pass an ApiClient or LoginClient instance around as props where needed. API calls from views will then always be a clean one liner, with zero code duplication.

EXAMPLE CODE

See this Curity code example. Note also that security related data is stored only in memory, which is often better from a security viewpoint. Tokens in local storage have greater XSS risks.

Use Distinct Axios instances for Secure API and Guest API

Follow below steps:

  1. Define baseURL for secure and guest APIs
  2. Create distinct Axios instances
  3. Define request, response and error handlers for secure API Axios instance
  4. Using request handler, put the token in request headers
  5. Bind above handlers as interceptors to the secure Axios instance
  6. Export both axios instances and use as required
  7. Clear the token upon logout or using timers

Sample code for axios.js

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

// Step 1
const baseURL = process.env.REACT_APP_BASE_URL;

// Step 2
const guestAxios = axios.create({ baseURL })
const secureAxios = axios.create({ baseURL })

// Step 3: Define handlers
const errorHandler = ()=>{
   ...
}

const secureAPIRequest= (config)=>{
   
    // Step 4: Put token in header
    const token = JSON.parse(localStorage.getItem('token'));
    config.headers.Authorization =  token ? `Bearer ${token}`: null;
    return config
}

const secureAPIResponse= ()=>{
   ...
}

// Step 5: Add interceptors for secure axios instance
secureAxios.interceptors.request.use(secureAPIRequest, errorHandler)
secureAxios.interceptors.response.use(secureAPIResponse, errorHandler)

// Step 6
export {
    guestAxios,
    secureAxios
}

Sample code for Login.js

import { guestAxios } from '/userLibrary/axios.js'
...

Sample code for AddSampleTable.js

import { secureAxios } from '/userLibrary/axios.js'
...

I suggest you to read this full example of react and jwt then you can check the diff with react-redux-jwt

Be careful about storing your token in redux, on each page refresh you will restart your redux store, if you have that as source of truth probably you will need to relogging your user each time (unwanted behavior usually). That's why you can use sessionStorage or localStorage or cookies

sessionStorage (as the name suggests) is only available for the duration of the browser session (and is deleted when the tab or window is closed) - it does, however, survive page reloads

localStorage is available even when closing the browser and opening it again, you have to removed it manually or with JS.

cookies are another option, with less size, shared between client and server, they travel in all the requests, it provides some expiration time and can be signed or encripted.

Related