I am working on a service class to create an instance of Axios (in React Native) where I'm using some interceptors to inject the JWT Token to authenticate against an API.
When the app detects that the access token
is expired, automatically use the refresh token
to request for a new token and a new refresh token at the same time.
The problem is when the refresh token become expired due to inactivity.
I want to redirect the user to the Login view component when the refresh token become expired.
What do I need to do to redirect to my Login view component taking into account that the service class is stateless and doesn't care what component it is called from?
This is what I have until now:
//API.js
import axios from 'axios';
import TokenStorage from './TokenStorage';
const API = axios.create({
responseType: 'json',
});
...
API.interceptors.response.use((response) => {
// Return a successful response back to the calling service
return response;
}, (error) => {
// Return any error which is not due to authentication back to the calling service
...
// Logout user if token refresh didn't work or user is disabled
if (error.config.url === '/tokens/refresh/' || error.response.data.code === 'access_invalid') {
TokenStorage.cleanStorage();
// TODO - Redirect to the "/login" route [Login View Component]
return new Promise((resolve, reject) => {
reject(error);
});
} else if (error.config.url === '/tokens/') {
// Reject if user credentials are wrong
return new Promise((resolve, reject) => {
reject(error);
});
}
// Try request again with new token
...
});
export default API;
Environment:
"axios": "^0.19.0",
"react": "16.9.0",
"react-native": "0.61.2",
"react-router-native": "^5.1.2"
PS: This is a very similar question to this, but I need a solution using React Router Native.
EDIT 1:
I already tried with the "history": "^4.10.1"
package as follow, with no success:
// API.js
import {createMemoryHistory} from 'history';
const history = createMemoryHistory();
...
// Logout user if token refresh didn't work or user is disabled
if (error.config.url === '/tokens/refresh/' || error.response.data.code === 'access_invalid') {
TokenStorage.cleanStorage();
history.push('/login'); // This don't work!
return new Promise((resolve, reject) => {
reject(error);
});
} else if (error.config.url === '/tokens/') {
// Reject if user credentials are wrong
return new Promise((resolve, reject) => {
reject(error);
});
}
Without Redux
//EventPublisher.js
export default class EventPublisher {
static instance;
listeners = {};
constructor(validEvents){
validEvents.forEach(ve=>{
this.listeners[ve] = []
})
}
subscribe(event, listener){
if(!this.listeners[event]) throw 'Invalid event'
this.listeners[event].push(listener)
return ()=>{
this.listeners[event].remove(listener)
}
}
publish(event){
this.listeners[event].forEach(l=>l())
}
}
In your top level component
componentDidMount(){
EventPublisher.instance = new EventPublisher([
'REFRESH_TOKEN',
])
this.cancelEventSubscription = EventPublisher.instance.subscribe('REFRESH_TOKEN', ()=>{
//Logic of react router native to navigate to LoginScreen
})
}
componentWillUnmount(){
this.cancelEventSubscription()
}
In axios
if (error.config.url === '/tokens/refresh/' || error.response.data.code === 'access_invalid') {
TokenStorage.cleanStorage();
// TODO - Redirect to the "/login" route [Login View Component]
EventPublisher.instance.publish('REFRESH_TOKEN')
return new Promise((resolve, reject) => {
reject(error);
});
} else if (error.config.url === '/tokens/') {
// Reject if user credentials are wrong
return new Promise((resolve, reject) => {
reject(error);
});
}