Best practice SPA per l'autenticazione e la gestione delle sessioni


309

Quando si creano applicazioni in stile SPA usando framework come Angular, Ember, React, ecc. Quali sono le persone che ritengono essere alcune best practice per l'autenticazione e la gestione delle sessioni? Mi viene in mente un paio di modi per considerare di affrontare il problema.

  1. Trattalo in modo non diverso dall'autenticazione con un'applicazione Web normale supponendo che l'API e l'interfaccia utente abbiano lo stesso dominio di origine.

    Ciò comporterebbe probabilmente un cookie di sessione, un archivio di sessioni lato server e probabilmente un endpoint dell'API di sessione che l'interfaccia utente Web autenticata può colpire per ottenere informazioni sull'utente corrente per aiutare con la personalizzazione o eventualmente determinare ruoli / abilità sul lato client. Il server applicherà comunque le regole che proteggono l'accesso ai dati ovviamente, l'interfaccia utente userebbe solo queste informazioni per personalizzare l'esperienza.

  2. Trattalo come qualsiasi client di terze parti che utilizza un'API pubblica e esegui l'autenticazione con una sorta di sistema token simile a OAuth. Questo meccanismo token verrebbe utilizzato dall'interfaccia utente del client per autenticare tutte le richieste fatte all'API del server.

Non sono un grande esperto qui, ma il n. 1 sembra essere completamente sufficiente per la stragrande maggioranza dei casi, ma mi piacerebbe davvero sentire alcune opinioni più esperte.


Risposte:


478

Questa domanda è stata affrontata, in una forma leggermente diversa, a lungo, qui:

Autenticazione RESTful

Ma questo si rivolge dal lato server. Diamo un'occhiata a questo dal lato client. Prima di farlo, però, c'è un preludio importante:

Javascript Crypto è senza speranza

L'articolo di Matasano su questo è famoso, ma le lezioni in esso contenute sono piuttosto importanti:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

Riassumere:

  • Un attacco man-in-the-middle può sostituire banalmente il tuo codice crittografico con <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Un attacco man-in-the-middle è banale contro una pagina che serve qualsiasi risorsa tramite una connessione non SSL.
  • Una volta che hai SSL, stai usando la vera crittografia comunque.

E per aggiungere un mio corollario:

  • Un attacco XSS riuscito può comportare l'esecuzione di codice da parte di un utente malintenzionato nel browser del client, anche se si utilizza SSL, quindi anche se si dispone di ogni tratteggio abbattuto, la crittografia del browser può comunque fallire se l'attaccante trova un modo per eseguire qualsiasi codice javascript sul browser di qualcun altro.

Ciò rende un sacco di schemi di autenticazione RESTful impossibili o sciocchi se si intende utilizzare un client JavaScript. Guardiamo!

HTTP Basic Auth

Innanzitutto, HTTP Basic Auth. Il più semplice degli schemi: passa semplicemente un nome e una password ad ogni richiesta.

Questo, ovviamente, richiede assolutamente SSL, perché stai passando un nome e una password con codifica Base64 (reversibilmente) ad ogni richiesta. Chiunque ascolta sulla linea potrebbe estrarre banalmente nome utente e password. La maggior parte degli argomenti "Basic Auth is insecure" provengono da un luogo di "Basic Auth over HTTP" che è una pessima idea.

Il browser fornisce il supporto HTTP Auth autenticato, ma è brutto come peccato e probabilmente non dovresti usarlo per la tua app. L'alternativa, tuttavia, è quella di nascondere nome utente e password in JavaScript.

Questa è la soluzione più RESTful. Il server non richiede alcuna conoscenza dello stato e autentica ogni singola interazione con l'utente. Alcuni appassionati di REST (per lo più uomini di paglia) insistono sul fatto che mantenere qualsiasi tipo di stato è un'eresia e si schiumerà alla bocca se si pensa a qualsiasi altro metodo di autenticazione. Ci sono vantaggi teorici in questo tipo di conformità agli standard - è supportato da Apache immediatamente - puoi memorizzare i tuoi oggetti come file in cartelle protette da file .htaccess se il tuo cuore lo desidera!

Il problema ? Stai memorizzando nella cache sul lato client un nome utente e una password. Questo dà a evil.ru una soluzione migliore - anche la più elementare delle vulnerabilità XSS potrebbe far sì che il client trasmetta il suo nome utente e password a un server malvagio. Puoi provare ad alleviare questo rischio eseguendo l'hashing e salando la password, ma ricorda: JavaScript Crypto è senza speranza . Potresti alleviare questo rischio lasciandolo al supporto Auth di base del Browser, ma ... brutto come peccato, come menzionato in precedenza.

HTTP Digest Auth

L'autenticazione Digest è possibile con jQuery?

Un'autenticazione più "sicura", questa è una sfida hash richiesta / risposta. Tranne JavaScript Crypto è senza speranza , quindi funziona solo su SSL e devi ancora memorizzare nella cache il nome utente e la password sul lato client, rendendolo più complicato di HTTP Basic Auth ma non più sicuro .

Autenticazione della query con parametri di firma aggiuntivi.

Un'altra autenticazione più "sicura", in cui si crittografano i parametri con dati nonce e timing (per proteggere da attacchi ripetuti e timing) e si invia il file. Uno dei migliori esempi di questo è il protocollo OAuth 1.0, che è, per quanto ne so, un modo piuttosto sbalorditivo per implementare l'autenticazione su un server REST.

http://tools.ietf.org/html/rfc5849

Oh, ma non ci sono client OAuth 1.0 per JavaScript. Perché?

JavaScript Crypto è senza speranza , ricorda. JavaScript non può partecipare a OAuth 1.0 senza SSL e devi ancora memorizzare il nome utente e la password del client localmente - il che lo pone nella stessa categoria di Digest Auth - è più complicato di HTTP Basic Auth ma non è più sicuro .

Gettone

L'utente invia un nome utente e una password e, in cambio, ottiene un token che può essere utilizzato per autenticare le richieste.

Questo è leggermente più sicuro di HTTP Basic Auth, perché una volta completata la transazione nome utente / password è possibile eliminare i dati sensibili. È anche meno RESTful, poiché i token costituiscono "stato" e rendono più complicata l'implementazione del server.

SSL ancora

Il problema però è che devi ancora inviare il nome utente e la password iniziali per ottenere un token. Le informazioni sensibili toccano ancora il tuo JavaScript compromettibile.

Per proteggere le credenziali del tuo utente, devi comunque tenere gli aggressori fuori dal tuo JavaScript e devi comunque inviare un nome utente e una password via cavo. SSL richiesto.

Scadenza token

È comune applicare politiche di token come "hey, quando questo token è in circolazione da troppo tempo, scartarlo e rendere nuovamente autentico l'utente". o "Sono abbastanza sicuro che l'unico indirizzo IP autorizzati a utilizzare questo token è XXX.XXX.XXX.XXX". Molte di queste politiche sono idee piuttosto buone.

Firesheeping

Tuttavia, l'utilizzo di un token senza SSL è ancora vulnerabile a un attacco chiamato "sidejacking": http://codebutler.github.io/firesheep/

L'attaccante non ottiene le credenziali dell'utente, ma può ancora fingere di essere il tuo utente, il che può essere piuttosto negativo.

tl; dr: l'invio di token non crittografati sul cavo significa che gli aggressori possono facilmente prendere quei token e fingere di essere i tuoi utenti. FireSheep è un programma che lo rende molto semplice.

Una zona separata, più sicura

Più grande è l'applicazione in esecuzione, più difficile sarà assolutamente assicurarsi che non siano in grado di iniettare del codice che modifica il modo in cui elaborate i dati sensibili. Ti fidi assolutamente della tua CDN? I tuoi inserzionisti? La tua base di codice?

Comune per i dettagli della carta di credito e meno comune per nome utente e password: alcuni implementatori mantengono l'inserimento dei dati sensibili in una pagina separata dal resto della loro applicazione, una pagina che può essere strettamente controllata e bloccata il meglio possibile, preferibilmente una che è difficile phishing con gli utenti.

Cookie (significa solo token)

È possibile (e comune) inserire il token di autenticazione in un cookie. Questo non cambia nessuna delle proprietà di auth con il token, è più una cosa comoda. Tutti gli argomenti precedenti si applicano ancora.

Sessione (significa ancora solo token)

Session Auth è solo l'autenticazione con token, ma con alcune differenze che la fanno sembrare una cosa leggermente diversa:

  • Gli utenti iniziano con un token non autenticato.
  • Il back-end mantiene un oggetto "stato" collegato al token di un utente.
  • Il token è fornito in un cookie.
  • L'ambiente di applicazione estrae i dettagli da te.

A parte questo, però, non è diverso da Token Auth, davvero.

Questo si allontana ancora di più da un'implementazione RESTful - con gli oggetti di stato stai andando sempre più avanti lungo il percorso del semplice RPC su un server stateful.

OAuth 2.0

OAuth 2.0 esamina il problema di "In che modo il software A consente al software B di accedere ai dati dell'utente X senza che il software B abbia accesso alle credenziali di accesso dell'utente X".

L'implementazione è semplicemente un modo standard per un utente di ottenere un token, e quindi per un servizio di terze parti andare "sì, questo utente e questo token corrispondono, e ora puoi ottenere alcuni dei loro dati da noi".

Fondamentalmente, tuttavia, OAuth 2.0 è solo un protocollo token. Presenta le stesse proprietà di altri protocolli token - hai ancora bisogno di SSL per proteggere quei token - cambia solo il modo in cui questi token vengono generati.

Esistono due modi in cui OAuth 2.0 può aiutarti:

  • Fornire autenticazione / informazioni ad altri
  • Ottenere autenticazione / informazioni da altri

Ma quando si tratta di esso, stai solo ... usando i token.

Torna alla tua domanda

Pertanto, la domanda che mi stai ponendo è "devo archiviare il token in un cookie e fare in modo che la gestione automatica della sessione del mio ambiente si occupi dei dettagli o devo archiviare il token in Javascript e gestirli da solo?"

E la risposta è: fai tutto ciò che ti rende felice .

La cosa sulla gestione automatica delle sessioni, tuttavia, è che sta accadendo molta magia dietro le quinte per te. Spesso è più bello avere il controllo di quei dettagli da soli.

Ho 21 anni, quindi SSL è sì

L'altra risposta è: utilizzare https per tutto o i briganti ruberanno le password e i token degli utenti.


3
Bella risposta. Apprezzo l'equivalenza tra i sistemi di autenticazione dei token e l'autent di base dei cookie (che è spesso integrato nel framework Web). Questo è quello che stavo cercando. Apprezzo che tu abbia trattato così tante potenziali questioni da considerare. Saluti!
Chris Nicola,

11
So che è passato un po 'di tempo, ma mi chiedo se questo dovrebbe essere ampliato per includere JWT? auth0.com/blog/2014/01/07/…
Chris Nicola

14
Token It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST richiede che il server sia stateless. Un lato client memorizzato token non rappresenta lo stato in alcun modo significativo per il server. (2) Il codice lato server leggermente più complicato non ha nulla a che fare con RESTfulness.
soupdog

10
lol_nope_send_it_to_me_insteadHo adorato il nome di questa funzione: D
Leone,

6
Una cosa che sembri trascurare: i cookie sono sicuri di XSS se contrassegnati come httpOnly e possono essere ulteriormente bloccati con secure e samesite. E la gestione dei cookie è in circolazione da molto più tempo === più battaglia indurita. Affidarsi a JS e all'archiviazione locale per gestire la sicurezza dei token è un gioco folle.
Martijn Pieters

57

È possibile aumentare la sicurezza nel processo di autenticazione utilizzando JWT (token Web JSON) e SSL / HTTPS.

L'ID di autenticazione / sessione di base può essere rubato tramite:

  • Attacco MITM (Man-In-The-Middle) - senza SSL / HTTPS
  • Un intruso che ottiene l'accesso al computer di un utente
  • XSS

Utilizzando JWT stai crittografando i dettagli di autenticazione dell'utente e memorizzandoli nel client e inviandoli insieme a ogni richiesta all'API, dove il server / API convalida il token. Non può essere decrittografato / letto senza la chiave privata (che il server / API archivia segretamente) Leggi aggiornamento .

Il nuovo flusso (più sicuro) sarebbe:

Accesso

  • L'utente accede e invia le credenziali di accesso all'API (su SSL / HTTPS)
  • L'API riceve le credenziali di accesso
  • Se valido:
    • Registra una nuova sessione nel database Leggi aggiornamento
    • Crittografa ID utente, ID sessione, indirizzo IP, timestamp, ecc. In un JWT con una chiave privata.
  • L'API invia il token JWT al client (su SSL / HTTPS)
  • Il client riceve il token JWT e lo memorizza in localStorage / cookie

Ogni richiesta all'API

  • L'utente invia una richiesta HTTP all'API (su SSL / HTTPS) con il token JWT memorizzato nell'intestazione HTTP
  • L'API legge l'intestazione HTTP e decodifica il token JWT con la sua chiave privata
  • L'API convalida il token JWT, abbina l'indirizzo IP della richiesta HTTP a quello nel token JWT e verifica se la sessione è scaduta
  • Se valido:
    • Risposta di ritorno con il contenuto richiesto
  • Se non valido:
    • Eccezione generata (403/401)
    • Segnala intrusione nel sistema
    • Invia un'e-mail di avviso all'utente.

Aggiornato il 30.07.15:

Il payload / attestazioni JWT può effettivamente essere letto senza la chiave privata (segreta) e non è sicuro archiviarlo in localStorage. Mi dispiace per queste false dichiarazioni. Tuttavia sembrano funzionare su uno standard JWE (JSON Web Encryption) .

L'ho implementato memorizzando i reclami (userID, exp) in un JWT, l'ho firmato con una chiave privata (segreta) di cui l'API / back-end conosce solo e l'ho archiviato come cookie HttpOnly sicuro sul client. In questo modo non può essere letto tramite XSS e non può essere manipolato, altrimenti JWT non riesce a verificare la firma. Inoltre, utilizzando un cookie HttpOnly sicuro , ti assicuri che il cookie venga inviato solo tramite richieste HTTP (non accessibili allo script) e inviato solo tramite connessione protetta (HTTPS).

Aggiornato il 17.07.16:

I JWT sono per natura apolidi. Ciò significa che si invalidano / scadono. Aggiungendo il SessionID nelle attestazioni del token, lo rendi stato, poiché la sua validità non dipende solo dalla verifica della firma e dalla data di scadenza, ma dipende anche dallo stato della sessione sul server. Tuttavia, il lato positivo è che puoi invalidare facilmente token / sessioni, cosa che non avresti mai potuto fare prima con JWT senza stato.


1
Alla fine un JWT è ancora "solo un token" dal punto di vista della sicurezza, credo. Il server potrebbe comunque associare l'id utente, l'indirizzo IP, il timestamp ecc. A un token di sessione opaco e non sarebbe più o meno sicuro di un JWT. Tuttavia, la natura apolide di JWT semplifica l'implementazione.
James,

1
@James il JWT ha il vantaggio di essere verificabile e in grado di trasportare dettagli chiave. Questo è piuttosto utile per vari scenari API, come ad esempio dove è richiesta l'autenticazione tra domini. Qualcosa per cui una sessione non sarà così buona. È anche una specifica definita (o almeno in corso), utile per le implementazioni. Ciò non significa che sia migliore di qualsiasi altra buona implementazione di token, ma è ben definita e conveniente.
Chris Nicola

1
@Chris Sì, sono d'accordo con tutti i tuoi punti. Tuttavia, il flusso descritto nella risposta sopra non è intrinsecamente un flusso più sicuro come affermato a causa dell'uso di un JWT. Inoltre, JWT non è revocabile nello schema sopra descritto a meno che non si associ un identificatore a JWT e si memorizzi lo stato sul server. In caso contrario, è necessario ottenere regolarmente un nuovo JWT richiedendo nome utente / password (esperienza utente scadente) o emettere un JWT con un tempo di scadenza molto lungo (non valido se il token viene rubato).
James

1
La mia risposta non è corretta al 100%, poiché JWT può effettivamente essere decrittografato / letto senza la chiave privata (segreta) e non è sicuro archiviarlo in localStorage. L'ho implementato memorizzando i reclami (userID, exp) in un JWT, l'ho firmato con una chiave privata (segreta) che l'API / backend conosce solo e lo ha archiviato come cookie HttpOnly sul client. In questo modo non può essere letto da XSS. Ma devi usare HTTPS perché il token potrebbe essere rubato con l'attacco MITM. Aggiornerò la mia risposta per riflettere su questo.
Gaui,

1
@vsenko Il cookie viene inviato con ogni richiesta dal cliente. Non accedi al cookie dal JS, è legato ad ogni richiesta HTTP dal client all'API.
Gaui,

7

Vorrei scegliere il secondo, il sistema token.

Sapevi di brace-auth o brace-semplici-auth ? Entrambi usano il sistema basato su token, come gli stati ember-simple-auth:

Una libreria leggera e discreta per l'implementazione dell'autenticazione basata su token nelle applicazioni Ember.js. http://ember-simple-auth.simplabs.com

Hanno una gestione delle sessioni e sono facili da collegare anche ai progetti esistenti.

Esiste anche una versione di esempio di Ember App Kit di ember-simple-auth: esempio funzionante di ember-app-kit che utilizza ember-simple-auth per l'autenticazione OAuth2.

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.