Best practice per la gestione lato server dei token JWT [chiuso]


111

(generato da questo thread poiché questa è davvero una questione a sé stante e non specifica per NodeJS ecc.)

Sto implementando un server API REST con autenticazione e ho implementato con successo la gestione dei token JWT in modo che un utente possa accedere tramite un endpoint / login con nome utente / password, su cui viene generato un token JWT da un segreto del server e restituito al cliente. Il token viene quindi passato dal client al server in ogni richiesta API autenticata, su cui viene utilizzato il segreto del server per verificare il token.

Tuttavia, sto cercando di capire le migliori pratiche per esattamente come e in che misura il token dovrebbe essere convalidato, per creare un sistema veramente sicuro. Cosa dovrebbe essere coinvolto esattamente nella "convalida" del token? È sufficiente che la firma possa essere verificata utilizzando il segreto del server, o devo anche controllare il token e / o il payload del token con alcuni dati memorizzati nel server?

Un sistema di autenticazione basato su token sarà sicuro quanto il passaggio di nome utente / password in ciascuna richiesta, a condizione che sia ugualmente o più difficile ottenere un token che ottenere la password di un utente. Tuttavia, negli esempi che ho visto, le uniche informazioni richieste per produrre un token sono il nome utente e il segreto lato server. Questo non significa che supponendo per un minuto che un utente malintenzionato acquisisca la conoscenza del segreto del server, ora può produrre token per conto di qualsiasi utente, avendo così accesso non solo a un dato utente come sarebbe il fatto se fosse una password ottenuto, ma appunto a tutti gli account utente?

Questo mi porta alle domande:

1) La convalida del token JWT dovrebbe essere limitata alla verifica della firma del token stesso, basandosi sull'integrità del solo segreto del server o accompagnata da un meccanismo di convalida separato?

  • In alcuni casi ho visto l'uso combinato di token e sessioni del server in cui dopo il login riuscito tramite l'endpoint / login viene stabilita una sessione. Le richieste API convalidano il token e confrontano anche i dati decodificati trovati nel token con alcuni dati archiviati nella sessione. Tuttavia, utilizzare le sessioni significa utilizzare i cookie e in un certo senso vanifica lo scopo dell'utilizzo di un approccio basato su token. Può anche causare problemi a determinati client.

  • Si potrebbe immaginare che il server mantenga tutti i token attualmente in uso in una memcache o simile, per garantire che anche se il segreto del server viene compromesso in modo che un utente malintenzionato possa produrre token "validi", solo i token esatti che sono stati generati tramite l'endpoint / login sarebbe accettato. Questo è ragionevole o semplicemente ridondante / eccessivo?

2) Se la verifica della firma JWT è l'unico mezzo per convalidare i token, il che significa che l'integrità del segreto del server è il punto di rottura, come dovrebbero essere gestiti i segreti del server? Leggere da una variabile di ambiente e creare (randomizzato?) Una volta per stack distribuito? Rinnovato o ruotato periodicamente (e in tal caso, come gestire i token validi esistenti che sono stati creati prima della rotazione ma devono essere convalidati dopo la rotazione, forse è sufficiente se il server mantiene il segreto corrente e precedente in un dato momento) ? Qualcos'altro?

Forse sono semplicemente eccessivamente paranoico quando si tratta del rischio che il segreto del server venga compromesso, che ovviamente è un problema più generale che deve essere affrontato in tutte le situazioni crittografiche ...


1
Ci sono grandi domande. Ri: domanda 2. Ho lo stesso problema con QUALSIASI chiave segreta tenuta lato server. Se stai eseguendo qualsiasi tipo di corrispondenza hash o decrittografia asimmetrica, sia che si tratti di firmare un jwt o di decrittografare le informazioni cc memorizzate nel db, devi avere una chiave segreta accessibile tramite codice sul server. Allora dove diavolo lo tieni ?? Ecco la migliore risposta che ho trovato: pcinetwork.org/forum/index.php?threads/… - probabilmente sicuro anche per una chiave jwt.
jbd

Qual è la chiave segreta nel token jwt? Sto pensando che jwt si sia considerato un segreto. O la chiave segreta potrebbe essere RSAPrivateKey privateKey??
kittu

3
Questo è stato chiesto tempo fa, ma forse qualcuno lo troverà utile. Nel mio caso, ho una "chiave segreta" per utente. Quindi ogni volta che un utente effettua il login, io genero quel segreto e lo memorizzo con il record dell'utente nel DB. Convalido il token usando quel segreto. Al momento del logout, annullo quel valore. Ciò invalida automaticamente altri token creati in precedenza (questo è ciò di cui avevo bisogno).
Nelson Rodriguez

Risposte:


52

Ho anche giocato con i gettoni per la mia applicazione. Anche se non sono affatto un esperto, posso condividere alcune delle mie esperienze e pensieri in merito.

Il punto di JWT è essenzialmente l'integrità. Fornisce un meccanismo per il tuo server per verificare che il token che gli è stato fornito sia autentico e sia stato fornito dal tuo server. La firma generata tramite il tuo segreto è ciò che prevede questo. Quindi, sì, se il tuo segreto è trapelato in qualche modo, quell'individuo può generare token che il tuo server penserebbe essere suoi. Un sistema basato su token sarebbe comunque più sicuro del tuo sistema di nome utente / password semplicemente a causa della verifica della firma. E in questo caso, se qualcuno ha comunque il tuo segreto, il tuo sistema ha altri problemi di sicurezza da affrontare rispetto a qualcuno che crea token falsi (e anche in questo caso, il solo cambiamento del segreto garantisce che tutti i token creati con il vecchio segreto ora non siano validi).

Per quanto riguarda il payload, la firma ti dirà solo che il token che ti è stato fornito era esattamente come era quando il tuo server lo ha inviato. verificare che i contenuti dei payload siano validi o appropriati per la tua applicazione spetta ovviamente a te.

Per le tue domande:

1.) Nella mia limitata esperienza, è decisamente meglio verificare i tuoi token con un secondo sistema. La semplice convalida della firma significa semplicemente che il token è stato generato con il tuo segreto. Memorizzare i token creati in una sorta di DB (redis, memcache / sql / mongo o qualche altro archivio) è un modo fantastico per assicurarti di accettare solo i token che il tuo server ha creato. In questo scenario, anche se il tuo segreto è trapelato, non avrà molta importanza poiché i token generati non saranno comunque validi. Questo è l'approccio che sto adottando con il mio sistema: tutti i token generati vengono memorizzati in un DB (redis) e ad ogni richiesta, verifico che il token sia nel mio DB prima di accettarlo. In questo modo i token possono essere revocati per qualsiasi motivo, come token che sono stati rilasciati in qualche modo, logout utente, modifiche password, modifiche segrete, ecc.

2.) Questo è qualcosa in cui non ho molta esperienza ed è qualcosa su cui sto ancora ricercando attivamente poiché non sono un professionista della sicurezza. Se trovi delle risorse, sentiti libero di pubblicarle qui! Attualmente, sto solo usando una chiave privata che carico da disco, ma ovviamente questa è lontana dalla soluzione migliore o più sicura.


5
Per il secondo punto ecco una buona risposta: security.stackexchange.com/questions/87130/…
Bossliaw

1
Poiché i token sono disponibili nell'intestazione, cosa succede se il token viene rubato e un malintenzionato tenta di accedere con quel token (essendo a conoscenza dell'indirizzo e-mail dell'utente)?
kittu

22
Se memorizzi ogni JWT, non ci sono vantaggi per JWT e potresti anche restare con ID di sessione casuali.
ColinM

46

Ecco alcune cose da considerare quando si implementano i JWT nella propria applicazione:

  • Mantieni la durata del tuo JWT relativamente breve e gestisci la durata sul server. In caso contrario, e in seguito sarà necessario richiedere ulteriori informazioni nei tuoi JWT, dovrai supportare 2 versioni o attendere che i tuoi JWT più vecchi siano scaduti prima di poter implementare la modifica. Puoi gestirlo facilmente sul server se guardi solo il iatcampo nel jwt e ignori il expcampo.

  • Considera l'idea di includere l'URL della richiesta nel tuo JWT. Ad esempio, se desideri che il tuo JWT venga utilizzato all'endpoint /my/test/path, includi un campo come 'url':'/my/test/path'nel tuo JWT, per assicurarti che venga utilizzato solo in questo percorso. Se non lo fai, potresti scoprire che le persone iniziano a utilizzare i tuoi JWT ad altri endpoint, anche quelli per cui non sono stati creati. Potresti anche considerare di includere un md5 (url), poiché avere un grande URL nel JWT finirà per rendere il JWT molto più grande e possono diventare piuttosto grandi.

  • La scadenza di JWT dovrebbe essere configurabile per ogni caso d'uso se i JWT vengono implementati in un'API. Ad esempio, se hai 10 endpoint per 10 diversi casi d'uso per JWT, assicurati di poter fare in modo che ogni endpoint accetti JWT che scadono in momenti diversi. Ciò consente di bloccare alcuni endpoint più di altri, se ad esempio i dati serviti da un endpoint sono molto sensibili.

  • Invece di scadere semplicemente i JWT dopo un certo tempo, prendi in considerazione l'implementazione di JWT che supportano entrambi:

    • N utilizzi: possono essere utilizzati solo N volte prima della scadenza e
    • scadono dopo un certo periodo di tempo (se hai un gettone usa e getta, non vuoi che viva per sempre se non viene utilizzato, vero?)
  • Tutti gli errori di autenticazione JWT dovrebbero generare un'intestazione di risposta "errore" che indica il motivo per cui l'autenticazione JWT non è riuscita. es. "scaduto", "nessun utilizzo rimasto", "revocato", ecc. Questo aiuta gli implementatori a sapere perché il loro JWT non funziona.

  • Considera l'idea di ignorare l '"intestazione" dei tuoi JWT in quanto perdono informazioni e danno una misura di controllo agli hacker. Ciò riguarda principalmente il algcampo nell'intestazione: ignoralo e presumi che l'intestazione sia ciò che desideri supportare, poiché ciò evita che gli hacker provino a utilizzare l' Nonealgoritmo, che rimuove il controllo di sicurezza della firma.

  • I JWT dovrebbero includere un identificatore che specifica quale app ha generato il token. Ad esempio, se i tuoi JWT vengono creati da 2 diversi client, mychat e myclassifiedsapp, ognuno dovrebbe includere il nome del progetto o qualcosa di simile nel campo "iss" nel JWT, ad esempio "iss": "mychat"

  • I JWT non devono essere registrati nei file di registro. È possibile registrare il contenuto di un JWT, ma non il JWT stesso. Ciò garantisce che gli sviluppatori o altri non possano prendere JWT dai file di registro e fare cose con gli account di altri utenti.
  • Assicurati che la tua implementazione JWT non consenta l'algoritmo "Nessuno", per evitare che gli hacker creino token senza firmarli. Questa classe di errori può essere completamente evitata ignorando l '"intestazione" del tuo JWT.
  • Considera vivamente di utilizzare iat(rilasciato a) anziché exp(scadenza) nei tuoi JWT. Perché? Poiché in iatpratica significa quando è stato creato il JWT, questo ti consente di regolare sul server quando scade il JWT, in base alla data di creazione. Se qualcuno passa in exp20 anni nel futuro, il JWT praticamente vive per sempre! Nota che fai scadere automaticamente i JWT se il loro iatè in futuro, ma lascia un po 'di margine di manovra (ad esempio 10 secondi), nel caso in cui l'ora del client sia leggermente fuori sincronia con l'ora del server.
  • Prendi in considerazione l'implementazione di un endpoint per la creazione di JWT da un payload json e costringi tutti i tuoi client di implementazione a utilizzare questo endpoint per creare i loro JWT. Ciò ti garantisce di poter risolvere facilmente qualsiasi problema di sicurezza che desideri con il modo in cui i JWT vengono creati in un unico posto. Non l'abbiamo fatto direttamente nella nostra app e ora dobbiamo distribuire lentamente gli aggiornamenti di sicurezza lato server JWT perché i nostri 5 diversi client hanno bisogno di tempo per l'implementazione. Inoltre, fai in modo che il tuo endpoint di creazione accetti un array di payload json per i JWT da creare e questo ridurrà il numero di richieste http in arrivo a questo endpoint per i tuoi client.
  • Se i tuoi JWT verranno utilizzati negli endpoint che supportano anche l'uso per sessione, assicurati di non inserire nulla nel tuo JWT che è necessario per soddisfare la richiesta. Puoi farlo facilmente se ti assicuri che il tuo endpoint funzioni con una sessione, quando non viene fornito alcun JWT.
  • Quindi, in generale, i JWT finiscono per contenere un ID utente o un ID gruppo di qualche tipo e consentono l'accesso a una parte del sistema in base a queste informazioni. Assicurati di non consentire agli utenti in un'area della tua app di impersonare altri utenti, soprattutto se ciò fornisce l'accesso a dati sensibili. Perché? Bene, anche se il processo di generazione di JWT è accessibile solo ai servizi "interni", gli sviluppatori o altri team interni potrebbero generare JWT per accedere ai dati di qualsiasi utente, ad esempio il CEO di un'azienda di un cliente casuale. Ad esempio, se la tua app fornisce l'accesso ai record finanziari per i clienti, generando un JWT, uno sviluppatore potrebbe afferrare i record finanziari di qualsiasi azienda! E se un hacker entra comunque nella tua rete interna, potrebbe fare lo stesso.
  • Se hai intenzione di consentire in qualsiasi modo la memorizzazione nella cache di qualsiasi URL che contiene un JWT, assicurati che le autorizzazioni per i diversi utenti siano incluse nell'URL e non nel JWT. Perché? Perché gli utenti potrebbero finire per ottenere dati che non dovrebbero. Ad esempio, supponiamo che un super utente acceda alla tua app e richieda il seguente URL: /mysite/userInfo?jwt=XXXe che questo URL venga memorizzato nella cache. Si disconnettono e un paio di minuti dopo, un utente normale accede alla tua app. Riceveranno il contenuto memorizzato nella cache, con informazioni su un super utente! Ciò tende ad accadere meno sul client e di più sul server, specialmente nei casi in cui si utilizza una CDN come Akamai e si lascia che alcuni file vivano più a lungo. Questo può essere risolto includendo le informazioni utente pertinenti nell'URL e convalidandolo sul server, anche per le richieste memorizzate nella cache, ad esempio/mysite/userInfo?id=52&jwt=XXX
  • Se il tuo jwt è inteso per essere usato come un cookie di sessione e dovrebbe funzionare solo sulla stessa macchina per cui è stato creato jwt, dovresti considerare di aggiungere un campo jti al tuo jwt. Questo è fondamentalmente un token CSRF, che garantisce che il tuo JWT non possa essere passato dal browser di un utente ad un altro.

1
Quello a cui ti riferisci created_by, c'è già un reclamo per quello in JWT e si chiama iss(emittente).
Fred

sì buon punto - aggiornerò con quello ... grazie!
Brad Parks

8

Non credo di essere un esperto, ma vorrei condividere alcuni pensieri su Jwt.

  • 1: Come ha detto Akshay, è meglio avere un secondo sistema per convalidare il tuo token.

    a .: Il modo in cui lo gestisco: memorizzo l'hash generato in una memoria di sessione con il tempo di scadenza. Per convalidare un token, è necessario che sia stato emesso dal server.

    b.:C'è almeno una cosa che deve essere controllata il metodo di firma utilizzato. per esempio :

    header :
    {
      "alg": "none",
      "typ": "JWT"
    }
    

Alcune librerie che convalidano JWT lo accetterebbero senza controllare l'hash. Ciò significa che senza conoscere il sale utilizzato per firmare il token, un hacker potrebbe concedersi alcuni diritti. Assicurati sempre che questo non possa accadere. https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/

c .: L'utilizzo di un cookie con un ID di sessione non sarebbe utile per convalidare il tuo token. Se qualcuno vuole dirottare la sessione di un utente lambda, dovrebbe solo usare uno sniffer (ad esempio: wirehark). Questo hacker avrebbe entrambe le informazioni contemporaneamente.

  • 2: È lo stesso per ogni segreto. C'è sempre un modo per saperlo.

Il modo in cui lo gestisco è collegato al punto 1.a. : Ho un segreto mescolato con una variabile casuale. Il segreto è unico per ogni gettone.

Tuttavia, sto cercando di capire le migliori pratiche per esattamente come e in che misura il token dovrebbe essere convalidato, per creare un sistema veramente sicuro.

Se desideri la massima sicurezza possibile, non dovresti seguire ciecamente le migliori pratiche. Il modo migliore è capire cosa stai facendo (penso che vada bene quando vedo la tua domanda) e quindi valutare la sicurezza di cui hai bisogno. E se il Mossad vuole avere accesso ai tuoi dati riservati, troverà sempre un modo. (Mi piace questo post del blog: https://www.schneier.com/blog/archives/2015/08/mickens_on_secu.html )


Un buon punto per avere un segreto unico per ogni gettone, ma come si crea ogni volta un segreto unico? Sto usando la libreria jwt di nimbus
kittu

1
probabilmente usa la password hash dell'utente.
momokjaaaaa

1
"Se non stai facendo le cose nello stesso modo in cui le fanno le altre persone, sarà più difficile per le persone trovare un modo per superare la tua sicurezza". Questo suona come Security Through Obscurity per me. Le migliori pratiche sono chiamate così perché mitigano i rischi più comuni in modo pratico.
Mnebuerquo

@Mnebuerquo Sono totalmente d'accordo con te, il ragazzo che ha scritto che non dovrebbe essere attendibile ;-)
Deblaton Jean-Philippe

1
Ha ragione, tuttavia, che non si dovrebbero seguire ciecamente le migliori pratiche. È bene capire perché le migliori pratiche sono considerate le migliori . In ogni decisione di progettazione della sicurezza c'è un compromesso tra sicurezza e usabilità. Capire il motivo significa che puoi prendere queste decisioni in modo intelligente. (Continua a seguire le migliori pratiche perché i tuoi utenti non lo faranno.)
Mnebuerquo

3

Molte buone risposte qui. Integrerò alcune delle risposte che ritengo più rilevanti e aggiungerò altri suggerimenti.

1) La convalida del token JWT dovrebbe essere limitata alla verifica della firma del token stesso, basandosi sull'integrità del solo segreto del server o accompagnata da un meccanismo di convalida separato?

No, per motivi estranei alla compromissione di un token secret. Ogni volta che un utente accede tramite un nome utente e una password, il server delle autorizzazioni deve archiviare il token generato oppure i metadati relativi al token generato. Pensa a questi metadati come a un record di autorizzazione. Una determinata coppia utente e applicazione deve avere un solo token valido, o autorizzazione, in un dato momento. Metadati utili sono l'ID utente associato al token di accesso, l'ID app e l'ora in cui è stato emesso il token di accesso (che consente la revoca dei token di accesso esistenti e l'emissione di un nuovo token di accesso). In ogni richiesta API, verifica che il token contenga i metadati appropriati. È necessario conservare le informazioni su quando è stato emesso ciascun token di accesso, in modo che un utente possa revocare i token di accesso esistenti se le credenziali del proprio account sono compromesse, eseguire nuovamente l'accesso e iniziare a utilizzare un nuovo token di accesso. Ciò aggiornerà il database con l'ora in cui è stato emesso il token di accesso (l'ora di autorizzazione creata). In ogni richiesta API, controlla che l'ora di rilascio del token di accesso sia successiva all'ora di autorizzazione creata.

Altre misure di sicurezza includevano la non registrazione di JWT e la richiesta di un algoritmo di firma sicuro come SHA256.

2) Se la verifica della firma JWT è l'unico mezzo per convalidare i token, il che significa che l'integrità del segreto del server è il punto di rottura, come dovrebbero essere gestiti i segreti del server?

La compromissione dei segreti del server consentirebbe a un utente malintenzionato di emettere token di accesso per qualsiasi utente e l'archiviazione dei dati del token di accesso nel passaggio 1 non impedirebbe necessariamente al server di accettare tali token di accesso. Ad esempio, si supponga che a un utente sia stato rilasciato un token di accesso e, successivamente, un utente malintenzionato generi un token di accesso per quell'utente. Il tempo di autorizzazione del token di accesso sarebbe valido.

Come dice Akshay Dhalwala, se il tuo segreto lato server è compromesso, hai problemi più grandi da affrontare perché significa che un utente malintenzionato ha compromesso la tua rete interna, il tuo repository di codice sorgente o entrambi.

Tuttavia, un sistema per mitigare il danno di un segreto del server compromesso ed evitare di memorizzare segreti nel codice sorgente comporta la rotazione del token segreto utilizzando un servizio di coordinamento come https://zookeeper.apache.org. Utilizza un cron job per generare un segreto dell'app ogni poche ore circa (per quanto tempo sono validi i tuoi token di accesso) e invia il segreto aggiornato a Zookeeper. In ogni server delle applicazioni che deve conoscere il token segreto, configurare un client ZK che viene aggiornato ogni volta che il valore del nodo ZK cambia. Memorizza un segreto primario e uno secondario e ogni volta che il segreto del token viene modificato, imposta il nuovo segreto del token sul primario e il vecchio segreto del token sul secondario. In questo modo, i token validi esistenti saranno ancora validi perché verranno convalidati rispetto al segreto secondario. Nel momento in cui il segreto secondario viene sostituito con il vecchio segreto primario, tutti i token di accesso emessi con il segreto secondario sarebbero comunque scaduti.


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.