2

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

Yulio Aleman Jimenez
  • 1,642
  • 3
  • 17
  • 33
  • Have you tried redux? – OriHero Dec 02 '19 at 19:40
  • From where you call API.js file?? – Vishal Dhanotiya Dec 07 '19 at 09:11
  • Usually from class and function components. I use It most of time in `componentDidMount` methods to load data from external API, and related functions that makes some kind of fetching to/from an API. – Yulio Aleman Jimenez Dec 07 '19 at 14:36
  • Does this answer your question? [Redirect to screen from an api interceptor (outside of component) in React Native](https://stackoverflow.com/questions/54232213/redirect-to-screen-from-an-api-interceptor-outside-of-component-in-react-nativ) – Zahir Masoodi Feb 04 '23 at 13:17
  • 1
    @ZahirMasoodi this is an old question but, if you read it carefully, you can see that I checked the answer you suggest, but in my code I needed a solution with React Router Native. Your suggested solution use React Navigation. Cheers. – Yulio Aleman Jimenez Feb 05 '23 at 14:13

1 Answers1

2

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