Hi I have implemented same scenario in react/redux app. But it would help you to achieve the goal. You don’t need to check 401 in each API call. Just implement it in your first validation API request. You can use setTimeOut to send refresh token api request before some time of authentication token expiry. So locatStorage will get updated and All axios requests won’t get expired token ever.
Here is my solution:
in my Constants.js
I;m maintaining USER TOKEN in localStorage like this:
export const USER_TOKEN = {
set: ({ token, refreshToken }) => {
localStorage.setItem('access_token', token);
localStorage.setItem('refresh_token', refreshToken);
},
remove: () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
},
get: () => ({
agent: 'agent',
token: localStorage.getItem('access_token'),
refreshToken: localStorage.getItem('refresh_token'),
}),
get notEmpty() {
return this.get().token !== null;
},
};
export const DEFAULT_HEADER = {
get: () => ({
'Content-type': 'application/json;charset=UTF-8',
agent: `${USER_TOKEN.get().agent}`,
access_token: `${USER_TOKEN.get().token}`,
}),
};
on page load, User Validate API request is as follows:
dispatch(actions.validateUser(userPayload)) // First time authentication with user credentials and it return access token, refresh token and expiry time
.then(userData => {
const { expires_in, access_token, refresh_token } = userData
USER_TOKEN.set({ // setting tokens in localStorage to accessible to all API calls
token: access_token,
refreshToken: refresh_token,
});
const timeout = expires_in * 1000 - 60 * 1000; // you can configure as you want but here it is 1 min before token will get expired
this.expiryTimer = setTimeout(() => { // this would reset localStorage before token expiry timr
this.onRefreshToken();
}, timeout);
}).catch(error => {
console.log("ERROR", error)
});
onRefreshToken = () => {
const { dispatch } = this.props;
const refresh_token = USER_TOKEN.get().refreshToken;
dispatch(actions.refreshToken({ refresh_token })).then(userData => {
const { access_token, refresh_token } = userData
USER_TOKEN.set({
token: access_token,
refreshToken: refresh_token,
});
});
};
Feel free to ask any questions, The other way is to implement axios abort controller to cancel pending promises. Happy to help with that too !
EDITED – You can maintain axios token source in all you API requests to abort them anytime.
maintain axios token source in all of your apis. once you get first promise resolved then you can cancel all other pending APIs request. You can invoke onAbort method in after your first promise gets resolved. See this:
//in your component
class MyComponent extends Component{
isTokenSource = axios.CancelToken.source(); // a signal you can point to any API
componentDidMount{
// for example if you're sending multiple api call here
this.props.dispatch(actions.myRequest(payload, this.isTokenSource.token))
.then(() => {
// all good
})
.catch(error => {
if (axios.isCancel(error)) {
console.warn('Error', error);
}
});
}
onAbortStuff = () => { // cancel request interceptor
console.log("Aborting Request");
this.isTokenSource.cancel('API was cancelled'); // This will abort all the pending promises if you send the same token in multiple requests,
}
render(){
//
}
While in your axios request you can send token like this:
export const myRequest= (id, cancelToken) => {
const URL = `foo`;
return axios(URL, {
method: 'GET',
headers: DEFAULT_HEADER.get(),
cancelToken: cancelToken
})
.then(response => {
// handle success
return response.data;
})
.catch(error => {
throw error;
});
};
For reference you can this article it is very helpful in understanding of cancel subscriptions. https://medium.freecodecamp.org/how-to-work-with-react-the-right-way-to-avoid-some-common-pitfalls-fc9eb5e34d9e
You can do your routes structuring in this way:
index.js
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
App.js:
class App extends Component {
state = {
isAuthenticated: false,
};
componentDidMount() {
//authentication API and later you can setState isAuthenticate
}
render() {
const { isAuthenticated } = this.state;
return isAuthenticated ? <Routes /> : <Loading />;
}
If you still find any issue, I’m more than happy to help you with this.