in Architettura Software

Creare un interceptor con Axios

Axios è sicuramente un client HTTP (per il browser e node.js) molto valido per l’interazione con REST API.

Un interceptor è una funziona che può essere utilizzata per poter modificare la richiesta o la risposta verso/da un API.

L’utilizzo di un interceptor è molto utile soprattutto in fase di processi di autenticazione in presenza di token (bearer) chede devono essere refreshati in maniera trasparente rispetto all’utente.

Axios si presta piuttosto bene per la creazione di custom interceptor legati alle request e alle response HTTP.

Di fatto un interceptor è una parte di codice che viene eseguita prima o dopo la richiesta HTTP.

Normalmente l’interceptor legato alla request viene utilizzato per aggiungere il token di accesso alla chiamata.

L’interceptor legato alla risposta, viene utilizzato normalmente per cambiare la risposta (es. mappando il contenuto su un oggetto differente) oppure per la generazione di un nuovo token tramite il refresh token (nel caso in cui l’access token sia scaduto).

Ovviamente è possibile aggiungere anche un solo tipo di interceptor all’instanza del client Axios che verrà utilizzato.

L’implementazione di un interceptor passa dai seguenti steps:

  • creazione di un istanza di Axios, con la relativa configurazione
  • creazione della request, response e del gestore degli errori
  • implementazione degli interceptors
  • export della nuova istanza (creata al primo punto) ed utilizzo

Interceptor per la request

Vediamo ora la creazione di un semplice interceptor legato alla request e che ci consentirà di aggiungere il token ad ogni richiesta. Solitamente l’access token viene memorizato all’interno del local storage del browser, quindi verrà recuperato direttamente da qui.

import axios from 'axios'

// Add a request interceptor
axios.interceptors.request.use(
  config => {
    const token = localStorage. getItem("token");
    if (token) {
      config.headers['Authorization'] = 'Bearer ' + token
    }
    config.headers['Content-Type'] = 'application/json';
    return config
  },
  error => {
    Promise.reject(error)
  }
)

In particolare alla riga 6 viene recuperato il token memorizzato nel local storage del browser e nel caso in cui sia presente viene aggiunto nell’header della chiamata, come Bearer.

Alla riga 10 viene anche indicato il tipo di Content-Type che dovrà essere utilizzato: in questo caso json.

Allo stesso modo, possiamo procedere alla scrittura dell’interceptor legato alla response.

Interceptor per la response

axios.interceptors.response.use(
  response => {
    return response
  },
  function (error) {
    const originalRequest = error.config

    if (
      error.response.status === 401 &&
      originalRequest.url === 'APPURL/v1/auth/token'
    ) {
      router.push('/login')
      return Promise.reject(error)
    }

    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true
      const refreshToken = localStorage.getItem("refreshtoken")
      return axios
        .post('/auth/token', {
          refresh_token: refreshToken
        })
        .then(res => {
          if (res.status === 201) {
            localStorageService.setToken(res.data)
            axios.defaults.headers.common['Authorization'] =
              'Bearer ' + localStorage.getItem("token");
            return axios(originalRequest)
          }
        })
    }
    return Promise.reject(error)
  }
)

In questo caso, il codice si complica leggermente. Consideriamo la logica che sta dietro alla response. Nell’interceptor relativo alla request abbiamo inserito il token per poter accedere alla api protette.

Normalmente all’interno di un flusso di autenticazione è presente anche il refresh token che consente di generare l’access token nel caso in cui sia scaduto.

Anche in questo caso ipotizziamo di aver salvato il refresh token all’interno dello storage del browser (accessibile tramite “refreshtoken”). Vediamo ora di analizzare il flusso.

Se la risposta viene ottenuta senza problemi dalla chiamata di axios, non è necessaria nessuna operazione e viene restituita cosi’ com’è alla riga 3. Nel caso in cui si siano verificati degli errori, devono essere trattati, in particolare quelli legati al 401, ovvero utente non autenticato.

Nel nostro caso trattiamo due casi specifici:

  • codice 401 (non autenticato) e url del token uguale a APPURL/v1/auth/token
  • codice 401 (non autenticato) e !originalRequest._retry

Nel primo caso siamo in presenza di una nuova chiamata per la generazione del token: in questo caso l’utente viene ridirezionato verso la pagina di login tramite la router.push(…)

Nel secondo caso verifichiamo la chiamata originale e se non ci troviamo in un eventuale retry della stessa. In questo caso recuperiamo il valore del refresh token (riga 18) ed effettuiamo la chiamata per la generazione del nuovo token.

Nel caso in cui venga generato correttamente (codice 201 – riga 24) procediamo con l’aggiunta nell’header della richiesta originale del Bearer token appena generato.

Nel caso in cui l’errore sia differente, viene restituito senza ulteriori considerazioni.