JWT (Token Web JSON) prolungamento automatico della scadenza

Vorrei implementare l’autenticazione basata su JWT nella nostra nuova API REST. Ma poiché la scadenza è impostata nel token, è ansible prolungarla automaticamente? Non voglio che gli utenti debbano accedere dopo ogni X minuti se stavano utilizzando triggersmente l’applicazione in quel periodo. Quello sarebbe un enorme fallimento UX.

Ma prolungare la scadenza crea un nuovo token (e il vecchio è ancora valido fino alla scadenza). E generare un nuovo token dopo ogni richiesta mi sembra sciocco. Sembra un problema di sicurezza quando più di un token è valido allo stesso tempo. Ovviamente potrei invalidare il vecchio usato usando una lista nera ma avrei bisogno di memorizzare i token. E uno dei vantaggi di JWT non è l’archiviazione.

Ho trovato come Auth0 l’ha risolto. Utilizzano non solo token JWT ma anche un token di aggiornamento: https://docs.auth0.com/refresh-token

Ma ancora una volta, per implementare questo (senza Auth0) avrei bisogno di memorizzare i token di aggiornamento e mantenere la loro scadenza. Qual è il vero vantaggio allora? Perché non hai un solo token (non JWT) e mantieni la scadenza sul server?

Ci sono altre opzioni? L’utilizzo di JWT non è adatto per questo scenario?

Lavoro con Auth0 e sono stato coinvolto nella progettazione della funzione di aggiornamento dei token.

Tutto dipende dal tipo di applicazione e qui è il nostro approccio raccomandato.

Applicazioni Web

Un buon modello è quello di aggiornare il token prima che scada.

Impostare la scadenza del token su una settimana e aggiornare il token ogni volta che l’utente apre l’applicazione Web e ogni ora. Se un utente non apre l’applicazione per più di una settimana, dovrà riconnettersi e questa è un’applicazione UX accettabile.

Per aggiornare il token, l’API richiede un nuovo endpoint che riceve un JWT valido e non scaduto e restituisce lo stesso JWT firmato con il nuovo campo di scadenza. Quindi l’applicazione web memorizzerà il token da qualche parte.

Applicazioni mobili / native

La maggior parte delle applicazioni native effettua il login una sola volta e solo una volta.

L’idea è che il token di aggiornamento non scade mai e può essere scambiato sempre per un JWT valido.

Il problema con un token che non scade mai è che non significa mai mai. Cosa fai se perdi il telefono? Pertanto, deve essere in qualche modo identificabile dall’utente e l’applicazione deve fornire un modo per revocare l’accesso. Abbiamo deciso di utilizzare il nome del dispositivo, ad esempio “iPad di maryo”. Quindi l’utente può accedere all’applicazione e revocare l’accesso a “maryo’s iPad”.

Un altro approccio consiste nel revocare il token di aggiornamento su eventi specifici. Un evento interessante sta cambiando la password.

Crediamo che JWT non sia utile per questi casi d’uso, quindi usiamo una stringa generata a caso e la archiviamo dalla nostra parte.

Nel caso in cui si gestisca l’auth (cioè non si usi un provider come Auth0), quanto segue potrebbe funzionare:

  1. Emetti token JWT con scadenza relativamente breve, ad esempio 15 minuti.
  2. L’applicazione controlla la data di scadenza del token prima di qualsiasi transazione che richiede un token (il token contiene la data di scadenza). Se il token è scaduto, prima chiede all’API di “aggiornare” il token (questo viene fatto in modo trasparente per l’UX).
  3. L’API riceve la richiesta di aggiornamento del token, ma prima controlla il database utente per vedere se un flag ‘reauth’ è stato impostato su quel profilo utente (il token può contenere l’id utente). Se il flag è presente, viene negato l’aggiornamento del token, altrimenti viene emesso un nuovo token.
  4. Ripetere.

Il flag “reauth” nel back-end del database verrà impostato quando, ad esempio, l’utente ha reimpostato la propria password. Il flag viene rimosso quando l’utente effettua l’accesso la volta successiva.

Inoltre, diciamo che hai una politica in base alla quale un utente deve accedere almeno una volta ogni 72 ore. In tal caso, la logica di aggiornamento del token API controllerà anche l’ultima data di accesso dell’utente dal database utente e negherà / consentirà l’aggiornamento del token su tale base.

Stavo armeggiando quando spostavo le nostre applicazioni su HTML5 con apis RESTful nel backend. La soluzione che ho trovato è stata:

  1. Il client viene emesso con un token con un tempo di sessione di 30 minuti (o qualunque sia il tempo di sessione lato server usuale) dopo il login riuscito.
  2. Viene creato un timer lato client per chiamare un servizio per rinnovare il token prima del suo tempo di scadenza. Il nuovo token sostituirà l’esistente nelle chiamate future.

Come puoi vedere, questo riduce le richieste frequenti di token di aggiornamento. Se l’utente chiude il browser / l’app prima che la chiamata al rinnovo del token venga triggersta, il token precedente scadrà nel tempo e l’utente dovrà effettuare nuovamente l’accesso.

Una strategia più complicata può essere implementata per soddisfare l’inattività dell’utente (ad esempio trascurata una scheda del browser aperta). In tal caso, la chiamata di rinnovo del token deve includere il tempo di scadenza previsto che non deve superare il tempo di sessione definito. L’applicazione dovrà tenere traccia dell’ultima interazione dell’utente di conseguenza.

Non mi piace l’idea di impostare la scadenza a lungo termine, quindi questo approccio potrebbe non funzionare bene con le applicazioni native che richiedono l’autenticazione meno frequente.

Una soluzione alternativa per invalidare i JWT, senza alcuna ulteriore memorizzazione sicura sul back-end, è implementare una nuova colonna intera jwt_version nella tabella utenti. Se l’utente desidera disconnettersi o scadere i token esistenti, incrementa semplicemente il campo jwt_version .

Quando si genera un nuovo JWT, codificare jwt_version nel payload JWT, incrementando facoltativamente il valore in anticipo se il nuovo JWT deve sostituire tutti gli altri.

Durante la convalida del JWT, il campo jwt_version viene confrontato insieme a user_id e l’authorization viene concessa solo se corrisponde.

Buona domanda- e c’è una ricchezza di informazioni nella domanda stessa.

L’articolo Aggiorna ganci: quando utilizzarli e in che modo interagiscono con le JWT dà una buona idea per questo scenario. Alcuni punti sono: –

  • I token di aggiornamento portano le informazioni necessarie per ottenere un nuovo token di accesso.
  • I token di aggiornamento possono anche scadere, ma sono piuttosto longevi.
  • I token di aggiornamento sono solitamente soggetti a severi requisiti di archiviazione per garantire che non siano trapelati.
  • Possono anche essere inseriti nella lista nera dal server di authorization.

Dai anche un’occhiata a auth0 / angular-jwt angularjs

Per API Web. leggi Abilita token di aggiornamento OAuth nell’app AngularJS utilizzando ASP .NET Web API 2 e Owin

In realtà l’ho implementato in PHP usando il client Guzzle per creare una libreria client per l’API, ma il concetto dovrebbe funzionare per altre piattaforms.

Fondamentalmente, emetto due token, uno corto (5 minuti) uno e uno lungo che scade dopo una settimana. La libreria client utilizza il middleware per tentare un aggiornamento del token breve se riceve una risposta 401 ad alcune richieste. Riprenderà quindi la richiesta originale e, se è stata in grado di aggiornare, ottiene la risposta corretta, in modo trasparente all’utente. Se fallisce, invierà semplicemente il 401 all’utente.

Se il token breve è scaduto, ma ancora autentico e il token lungo è valido e autentico, aggiornerà il token breve utilizzando un endpoint speciale sul servizio che il token lungo autentica (questa è l’unica cosa per cui può essere utilizzato). Utilizzerà quindi il token breve per ottenere un nuovo token lungo, estendendolo in tal modo un’altra settimana ogni volta che aggiorna il token breve.

Questo approccio ci consente anche di revocare l’accesso entro al massimo 5 minuti, il che è accettabile per il nostro uso senza dover memorizzare una lista nera di token.

Modifica in ritardo: rileggere questo mese dopo che era fresco nella mia testa, vorrei sottolineare che è ansible revocare l’accesso quando si aggiorna il token breve perché dà l’opportunità di effettuare chiamate più costose (es. Chiamare il database per vedere se l’utente è stato vietato) senza pagare per ogni singola chiamata al tuo servizio.

JWT-autorefresh

Se si utilizza il nodo (React / Redux / Universal JS), è ansible installare npm i -S jwt-autorefresh .

Questa libreria pianifica l’aggiornamento dei token JWT a un numero calcolato dall’utente di secondi prima che il token di accesso scada (in base alla dichiarazione exp codificata nel token). Ha una vasta suite di test e controlla per un certo numero di condizioni per garantire che ogni strana attività sia accompagnata da un messaggio descrittivo riguardante le errate configurazioni del proprio ambiente.

Implementazione completa di esempio

 import autorefresh from 'jwt-autorefresh' /** Events in your app that are triggered when your user becomes authorized or deauthorized. */ import { onAuthorize, onDeauthorize } from './events' /** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */ const refresh = () => { const init = { method: 'POST' , headers: { 'Content-Type': `application/x-www-form-urlencoded` } , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token` } return fetch('/oauth/token', init) .then(res => res.json()) .then(({ token_type, access_token, expires_in, refresh_token }) => { localStorage.access_token = access_token localStorage.refresh_token = refresh_token return access_token }) } /** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */ const leadSeconds = () => { /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */ const jitter = Math.floor(Math.random() * 30) /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */ return 60 + jitter } let start = autorefresh({ refresh, leadSeconds }) let cancel = () => {} onAuthorize(access_token => { cancel() cancel = start(access_token) }) onDeauthorize(() => cancel()) 

disclaimer: io sono il manutentore

Che ne dici di questo approccio:

  • Per ogni richiesta del client, il server confronta la scadenza del token con (currentTime – lastAccessTime)
  • Se expirationTime <(currentTime - lastAccessedTime) , cambia l’ultimo lastAccessedTime in currentTime.
  • In caso di inattività sul browser per un periodo di tempo superiore alla scadenza o nel caso in cui la finestra del browser fosse chiusa e il tempo di scadenza> (currentTime – lastAccessedTime) , il server può scadere il token e chiedere all’utente di effettuare nuovamente il login.

In questo caso non è necessario un endpoint aggiuntivo per l’aggiornamento del token. Apprezzerei qualsiasi pacco.

Ho risolto questo problema aggiungendo una variabile nei dati del token:

 softexp - I set this to 5 mins (300 seconds) 

Ho impostato expiresIn opzione expiresIn al mio tempo desiderato prima che l’utente sia costretto ad accedere nuovamente. Il mio è impostato su 30 minuti. Questo deve essere maggiore del valore di softexp .

Quando l’app lato client invia una richiesta all’API del server (dove è richiesto token, ad esempio la pagina dell’elenco clienti), il server controlla se il token inviato è ancora valido o meno in base al valore di scadenza originale ( expiresIn ). Se non è valido, il server risponderà con uno stato particolare per questo errore, ad es. INVALID_TOKEN .

Se il token è ancora valido in base al valore expiredIn , ma ha già superato il valore softexp , il server risponderà con uno stato separato per questo errore, ad es. EXPIRED_TOKEN :

 (Math.floor(Date.now() / 1000) > decoded.softexp) 

Sul lato client, se ha ricevuto risposta EXPIRED_TOKEN , dovrebbe rinnovare automaticamente il token inviando una richiesta di rinnovo al server. Questo è trasparente per l’utente e viene automaticamente gestito dall’app client.

Il metodo di rinnovo nel server deve verificare se il token è ancora valido:

 jwt.verify(token, secret, (err, decoded) => {}) 

Il server rifiuterà di rinnovare i token se non ha superato il metodo precedente.