JWT (JSON Web Token) prolungamento automatico della scadenza


509

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

Ma il prolungamento della scadenza crea un nuovo token (e quello 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 contemporaneamente. Naturalmente potrei invalidare il vecchio usato usando una lista nera ma avrei bisogno di conservare i token. E uno dei vantaggi di JWT è l'archiviazione.

Ho trovato come Auth0 ha risolto il problema. Usano non solo il 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 quindi? Perché non avere un solo token (non JWT) e mantenere la scadenza sul server?

Ci sono altre opzioni? L'uso di JWT non è adatto a questo scenario?


1
In realtà probabilmente non ci sono problemi di sicurezza con molti token validi contemporaneamente ... In realtà ci sono un numero infinito di token validi ... Quindi, perché avere un token di aggiornamento allora? Li rigenererò dopo ogni richiesta, in realtà non dovrebbe essere un problema.
maryo,

1
Per SPA, controlla il mio post sul blog: blog.wong2.me/2017/02/20/refresh-auth0-token-in-spa
wong2

2
@maryo Penso che avere (potenzialmente) centinaia o migliaia di JWT validi non utilizzati là fuori in qualsiasi momento aumenti la tua impronta di attacco ed è un rischio per la sicurezza. Nella mia mente, i JWT dovrebbero essere emessi con attenzione in quanto sono token di accesso con le chiavi del castello in un certo modo.
java-addict301,

Risposte:


590

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

Tutto dipende dal tipo di applicazione ed ecco il nostro approccio raccomandato.

Applicazioni Web

Un buon modello è 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à effettuare nuovamente l'accesso e questo è accettabile UX dell'applicazione Web.

Per aggiornare il token, l'API necessita di un nuovo endpoint che riceva un JWT valido, non scaduto e restituisca 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 l'accesso una sola volta.

L'idea è che il token di aggiornamento non scade mai e può essere scambiato sempre con 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 identificabile dall'utente in qualche modo 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ò andare all'applicazione e revocare l'accesso a "iPad di maryo".

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

Riteniamo che JWT non sia utile per questi casi d'uso, quindi utilizziamo una stringa generata casualmente e la memorizziamo dalla nostra parte.


42
Per l'approccio consigliato per le applicazioni Web, se il token è valido per una settimana, non ci occupiamo di qualcuno che intercetta il token e che sia in grado di utilizzarlo per così tanto tempo? Disclaimer: non so proprio di cosa sto parlando.
user12121234

30
@wbeange sì l'intercettazione è un problema, anche con i cookie. Dovresti usare https.
José F. Romaniello,

15
@ JoséF.Romaniello Nel tuo esempio di applicazione web, tutto ha un senso per me tranne che dover memorizzare il token. Ho pensato che la bellezza di JWT fosse l'autenticazione senza stato, il che significa che l'applicazione Web NON deve archiviare il token mentre è firmato. Penso che il server potrebbe semplicemente verificare la validità del token, assicurarsi che sia entro il periodo di scadenza e quindi emettere un nuovo token JWT. Potresti per favore approfondire questo? Forse non capisco ancora abbastanza JWT.
Lo-Tan,

7
Due domande / dubbi: 1- Caso di applicazione Web: perché non è possibile ottenere un aggiornamento del token scaduto? Supponiamo di impostare una scadenza breve (1 ora) e di effettuare chiamate di rinnovo al server back-end quando scade un token, come hai detto. 2- C'è un problema di sicurezza con l'archiviazione della password con hash (con salt casuale) nel token? L'idea è che se è presente, il server back-end può verificare la password memorizzata nel DB quando viene richiesto un rinnovo e rifiutare la richiesta se le password non corrispondono. Ciò coprirebbe la modifica della password dell'app mobile / nativa, consentendo di estendere la soluzione al caso d'uso mobile.
psamaan,

8
-1 L'esposizione di un'API pubblica che rinnova ciecamente qualsiasi token per estendere il periodo di convalida non è valida. Ora tutti i token hanno una scadenza infinita effettiva. L'atto di firmare un token dovrebbe includere i controlli di autenticazione appropriati per ogni richiesta fatta in quel token al momento della firma.
Phil

69

Nel caso in cui gestisci tu stesso l'author (ovvero non usi un provider come Auth0), potrebbe funzionare quanto segue:

  1. Emettere un 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 richieda un token (il token contiene la data di scadenza). Se il token è scaduto, chiede innanzitutto all'API di "aggiornare" il token (questo viene fatto in modo trasparente alla UX).
  3. L'API ottiene la richiesta di aggiornamento del token, ma prima controlla il database dell'utente per vedere se è stato impostato un flag 'reauth' rispetto a quel profilo utente (il token può contenere ID utente). Se il flag è presente, viene negato l'aggiornamento del token, altrimenti viene emesso un nuovo token.
  4. Ripetere.

Il flag 'reauth' nel backend del database verrebbe impostato quando, ad esempio, l'utente ha reimpostato la propria password. Il flag viene rimosso quando l'utente accede la volta successiva.

Inoltre, supponiamo che tu abbia 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 controlla anche l'ultima data di accesso dell'utente dal database dell'utente e nega / consente l'aggiornamento del token su tale base.


7
Non penso che questo sarebbe sicuro. Se fossi un attaccante e rubassi il tuo token e lo inviassi al server, il server controllerebbe e vedrebbe il flag impostato su true, il che è fantastico in quanto bloccherebbe un aggiornamento. Il problema penso che sarebbe se la vittima avesse cambiato la password, il flag sarebbe stato impostato su false e ora l'attaccante può usare quel token originale per aggiornare.
user2924127

6
@utente2924127 nessuna soluzione di autenticazione è perfetta e ci saranno sempre dei compromessi. Se un attaccante è in grado di "rubare il tuo token", allora potresti avere maggiori problemi di cui preoccuparti. L'impostazione di una durata massima del token sarebbe utile per quanto sopra.
IanB,

27
invece di avere un altro campo nel database, reauth flag, puoi includere l'hash (bcrypt_password_hash) nel token. Quindi, quando si aggiorna il token, si conferma semplicemente se l'hash (bcrypt_password_hash) è uguale a un valore dal token. Per negare l'aggiornamento del token, è sufficiente aggiornare l'hash della password.
bas

4
@bas, pensando a ottimizzazioni e prestazioni, penso che la convalida dell'hash della password sarebbe ridondante e avrebbe più implicazioni sul server. Aumenta le dimensioni del token in modo che la firma / convalida della firma richieda più tempo. calcoli hash aggiuntivi per il server per la password. con l'approccio extra field ti basta validare nel ricalcolo con un semplice booleano. Gli aggiornamenti del database sono meno frequenti per il campo aggiuntivo, ma sono aggiornamenti del token più frequenti. E ottieni il servizio opzionale di forzare i singoli accessi per qualsiasi sessione esistente (mobile, web, ecc.).
le0diaz,

6
Penso che il primo commento dell'utente2924127 sia effettivamente sbagliato. Quando la password viene modificata, l'account viene contrassegnato come richiedente una nuova autenticazione, pertanto eventuali token scaduti esistenti non saranno validi.
Ralph,

15

Stavo armeggiando quando ho spostato le nostre applicazioni in HTML5 con API RESTful nel backend. La soluzione che mi è venuta in mente è stata:

  1. Al client viene emesso un token con un tempo di sessione di 30 minuti (o qualunque sia il normale tempo di sessione lato server) al momento dell'accesso riuscito.
  2. Viene creato un timer lato client per chiamare un servizio per rinnovare il token prima della scadenza. Il nuovo token sostituirà quello esistente nelle chiamate future.

Come puoi vedere, ciò riduce le frequenti richieste di token di aggiornamento. Se l'utente chiude il browser / l'app prima dell'attivazione della chiamata al token di rinnovo, il token precedente scadrà in tempo e l'utente dovrà effettuare nuovamente il login.

Una strategia più complicata può essere implementata per soddisfare l'inattività dell'utente (ad esempio trascurato una scheda del browser aperta). In tal caso, la chiamata del token di rinnovo dovrebbe 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 una scadenza lunga, quindi questo approccio potrebbe non funzionare bene con le applicazioni native che richiedono un'autenticazione meno frequente.


1
Cosa succede se il computer è stato sospeso / sospeso. Il timer continuerà a contare fino alla scadenza, ma il token era effettivamente già scaduto. Il timer non funziona in queste situazioni
Alex Parij,

@AlexParij Faresti un confronto con un tempo fisso, qualcosa del genere: stackoverflow.com/a/35182296/1038456
Aparajita,

2
Consentire al cliente di richiedere un nuovo token con una data di scadenza preferita mi sembra un rischio per la sicurezza.
java-addict301,

14

Una soluzione alternativa per invalidare i JWT, senza ulteriore spazio di archiviazione sicuro sul back-end, è implementare una nuova jwt_versioncolonna intera nella tabella degli utenti. Se l'utente desidera disconnettersi o scadere i token esistenti, aumenta semplicemente il jwt_versioncampo.

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

Quando si convalida il JWT, il jwt_versioncampo viene confrontato accanto a user_ide l'autorizzazione viene concessa solo se corrisponde.


1
Questo ha problemi con più dispositivi. In sostanza, se esci da un dispositivo, si disconnette ovunque. Giusto?
Sam Washburn,

4
Ehi, potrebbe non essere un "problema" a seconda delle tue esigenze, ma hai ragione; questo non supporta la gestione delle sessioni per dispositivo.
Ollie Bennett,

Questo non significa che jwt_version deve essere archiviato sul lato server in modo tale che lo schema di autenticazione diventi "simile alla sessione" e vanifichi lo scopo fondamentale dei JWT?
ChetPrickles,

8

Buona domanda - e ci sono molte informazioni nella domanda stessa.

L'articolo Aggiorna token: quando usarli e come interagiscono con i JWT offre una buona idea per questo scenario. Alcuni punti sono: -

  • I token di aggiornamento contengono 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 generalmente soggetti a rigidi requisiti di archiviazione per garantire che non vi siano perdite.
  • Possono anche essere inseriti nella blacklist dal server di autorizzazione.

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

Per l'API Web. leggi Abilita token di aggiornamento OAuth nell'app AngularJS usando l'API Web ASP .NET 2 e Owin


Forse l'ho letto male ... Ma l'articolo con un titolo che inizia come "Aggiorna token ..." non contiene nulla sui token di aggiornamento, tranne quello che hai menzionato qui.
Ievgen Martynov,

8

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 piattaforme.

Fondamentalmente, lancio due token, uno corto (5 minuti) 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 a una richiesta. Quindi tenterà di nuovo la richiesta originale e se è stato in grado di aggiornare ottiene la risposta corretta, in modo trasparente per l'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 autenticato dal token lungo (questa è l'unica cosa per cui può essere utilizzato). Quindi utilizzerà 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 un massimo di 5 minuti, il che è accettabile per il nostro uso senza dover memorizzare una lista nera di token.

Modifica tardiva: rileggendo questi mesi dopo che era fresco nella mia testa, dovrei sottolineare che puoi revocare l'accesso quando aggiorni il token breve perché offre l'opportunità di chiamate più costose (ad esempio, chiama il database per vedere se l'utente è stato bannato) senza pagare per ogni singola chiamata al tuo servizio.


8

Di seguito sono riportati i passaggi per revocare il token di accesso JWT:

1) Quando si effettua l'accesso, inviare 2 token (token di accesso, token di aggiornamento) in risposta al client.
2) Il token di accesso avrà meno tempo di scadenza e Refresh avrà un lungo tempo di scadenza.
3) Il client (front-end) memorizzerà il token di aggiornamento nella sua memoria locale e accederà al token nei cookie.
4) Il client utilizzerà il token di accesso per chiamare le API. Ma quando scade, seleziona il token di aggiornamento dalla memoria locale e chiama l'API del server di autenticazione per ottenere il nuovo token.
5) Il tuo server di autenticazione avrà un'API esposta che accetterà il token di aggiornamento e ne verificherà la validità e restituirà un nuovo token di accesso.
6) Una volta scaduto il token di aggiornamento, l'utente verrà disconnesso.

Per favore fatemi sapere se avete bisogno di maggiori dettagli, posso condividere anche il codice (Java + Spring boot).


Potresti condividere il link del tuo progetto se lo hai in GitHub?
Arun Kumar N,


6

JWT-autorefresh

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

Questa libreria pianifica l'aggiornamento dei token JWT in base a un numero calcolato di secondi dall'utente prima della scadenza del token di accesso (in base alla rivendicazione exp codificata nel token). Ha una vasta suite di test e verifica alcune condizioni per garantire che qualsiasi strana attività sia accompagnata da un messaggio descrittivo relativo alle configurazioni errate del tuo 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: sono il manutentore


Domanda su questo, ho visto la funzione di decodifica che utilizza. Suppone che il JWT possa essere decodificato senza usare un segreto? Funziona con JWT che è stato firmato con un segreto?
Gian Franco Zabarino,

3
Sì, la decodifica è una decodifica solo client e non deve essere a conoscenza del segreto. Il segreto viene utilizzato per firmare il token JWT lato server per verificare che la firma sia stata utilizzata per generare il JWT in origine e non debba mai essere utilizzata dal client. La magia di JWT è che il suo payload può essere decodificato sul lato client e le rivendicazioni all'interno possono essere utilizzate per costruire la tua interfaccia utente senza il segreto. L'unica cosa che lo jwt-autorefreshdecodifica è estrarre il expreclamo in modo che possa determinare fino a che punto pianificare il prossimo aggiornamento.
cchamberlain,

1
Oh buono a sapersi, qualcosa non aveva senso, ma ora lo fa. Grazie per la risposta.
Gian Franco Zabarino,

4

Ho risolto questo problema aggiungendo una variabile nei dati token:

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

Ho impostato l' expiresInopzione sul 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 la mia app lato client invia una richiesta all'API del server (dove è richiesto il token, ad esempio la pagina dell'elenco clienti), il server verifica se il token inviato è ancora valido o meno in base al expiresInvalore di scadenza ( ) originale . 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 expiredInvalore, ma ha già superato il softexpvalore, 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 EXPIRED_TOKENrisposta, dovrebbe rinnovare automaticamente il token inviando una richiesta di rinnovo al server. Questo è trasparente per l'utente e viene automaticamente curato 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 sopra.


Questa strategia sembra buona. Ma penso che dovrebbe essere integrato con una sorta di "numero massimo di rinnovi" perché (forse) un utente può essere vivo per sempre.
Juan Ignacio Barisich,

1
È possibile impostare una variabile hardExp nei dati del token per impostare una data massima per forzare la scadenza del token o forse un contatore che viene decrementato ogni volta che il token viene rinnovato, limitando la quantità di rinnovi totali del token.
James, un

1
è corretto. Lo considero un "must".
Juan Ignacio Barisich,

2

Che ne dici di questo approccio:

  • Per ogni richiesta client, il server confronta il tempo di 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 a ScadenzaTempo o nel caso in cui la finestra del browser fosse chiusa e ScadenzaTempo> (currentTime - lastAccessedTime) , quindi il server può scadere il token e chiedere all'utente di accedere nuovamente.

In questo caso non è necessario un punto finale aggiuntivo per l'aggiornamento del token. Gradirei qualsiasi feedack.


È una buona scelta in questo giorno, è abbastanza facile da implementare.
n.

4
In questo caso, dove memorizzi lastAccessedTime? Devi farlo su backend e per richiesta, quindi diventa una soluzione stateful non desiderata.
antgar9

2

Oggi, molte persone optano per la gestione delle sessioni con JWT senza essere consapevoli di ciò che stanno cedendo per il gusto della semplicità percepita . La mia risposta elabora la seconda parte delle domande:

Qual è il vero vantaggio quindi? Perché non avere un solo token (non JWT) e mantenere la scadenza sul server?

Ci sono altre opzioni? L'uso di JWT non è adatto a questo scenario?

I JWT sono in grado di supportare la gestione delle sessioni di base con alcune limitazioni. Essendo token auto-descrittivi, non richiedono alcun stato sul lato server. Questo li rende attraenti. Ad esempio, se il servizio non ha un livello di persistenza, non è necessario attivarne uno solo per la gestione della sessione.

Tuttavia, l'apolidia è anche la principale causa delle loro carenze. Dal momento che vengono emessi una sola volta con contenuto fisso e scadenza, non è possibile eseguire le operazioni desiderate con una tipica configurazione di gestione della sessione.

Vale a dire, non è possibile invalidarli su richiesta. Ciò significa che non è possibile implementare un logout sicuro in quanto non è possibile far scadere i token già emessi. Inoltre, non è possibile implementare il timeout di inattività per lo stesso motivo. Una soluzione è quella di mantenere una lista nera, ma ciò introduce lo stato.

Ho scritto un post che spiega questi inconvenienti in modo più dettagliato. Per essere chiari, puoi aggirare questi problemi aggiungendo più complessità (sessioni scorrevoli, token di aggiornamento, ecc.)

Per quanto riguarda le altre opzioni, se i tuoi clienti interagiscono solo con il tuo servizio tramite un browser, ti consiglio vivamente di utilizzare una soluzione di gestione delle sessioni basata su cookie. Ho anche compilato un elenco di metodi di autenticazione attualmente ampiamente utilizzati sul web.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.