passport.js Autenticazione RESTful


155

Come si gestisce l'autenticazione (locale e Facebook, ad esempio) utilizzando passport.js, tramite un'API RESTful anziché tramite un'interfaccia Web?

Preoccupazioni specifiche sono la gestione del passaggio di dati dai callback a una risposta RESTful (JSON) rispetto all'uso di un tipico res.send ({data: req.data}), la configurazione di un endpoint iniziale / di accesso che reindirizza a Facebook (/ login non può essere accessibile tramite AJAX, perché non è una risposta JSON - è un reindirizzamento a Facebook con un callback).

Ho trovato https://github.com/halrobertson/test-restify-passport-facebook , ma ho difficoltà a capirlo.

Inoltre, in che modo passport.js memorizza le credenziali di autenticazione? Il server (o è un servizio?) È supportato da MongoDB e mi aspetto che le credenziali (login e hash salato di pw) vengano archiviate lì, ma non so se passport.js ha questo tipo di funzionalità.


Dato che non conosci Node, inizia facilmente e controlla l'applicazione di esempio per passport-facebook. Dopo aver funzionato, il passaggio successivo è iniziare a capire come funziona Passport e come memorizza le credenziali. Collegarlo a Restify ( vedi qui per una versione aggiornata di quella che hai citato) sarebbe uno degli ultimi passaggi (o potresti implementare l'interfaccia REST in Express).
robertklep,

Risposte:


312

Ci sono molte domande qui poste, e sembra che anche se le domande siano poste nel contesto di Node e passport.js, le vere domande riguardano più il flusso di lavoro che come farlo con una particolare tecnologia.

Usiamo l'impostazione di esempio @Keith, modificata un po 'per una maggiore sicurezza:

  • Il server Web https://example.comserve un'app client Javascript a pagina singola
  • Il servizio Web RESTful su https://example.com/apifornisce supporto server all'app rich client
  • Server implementato in Node e passport.js.
  • Il server ha un database (qualsiasi tipo) con una tabella "utenti".
  • Nome utente / password e Facebook Connect sono offerti come opzioni di autenticazione
  • Rich client effettua richieste REST in https://example.com/api
  • Potrebbero esserci altri client (app telefoniche, ad esempio) che utilizzano il servizio Web all'indirizzo https://example.com/apima non conoscono il server Web all'indirizzo https://example.com.

Nota che sto usando HTTP sicuro. Questo è secondo me un must per qualsiasi servizio disponibile all'aperto, poiché informazioni sensibili come password e token di autorizzazione passano tra client e server.

Autenticazione nome utente / password

Diamo un'occhiata a come funziona per prima la semplice autenticazione.

  • L'utente si connette a https://example.com
  • Il server serve una ricca applicazione Javascript che rende la pagina iniziale. Da qualche parte nella pagina c'è un modulo di login.
  • Molte sezioni di questa app a pagina singola non sono state popolate con dati a causa della mancata connessione dell'utente. Tutte queste sezioni hanno un listener di eventi su un evento "login". Tutto questo è roba lato client, il server non è a conoscenza di questi eventi.
  • L'utente inserisce login e password e preme il pulsante di invio, che attiva un gestore Javascript per registrare il nome utente e la password nelle variabili lato client. Quindi questo gestore attiva l'evento "login". Ancora una volta, questa è tutta l'azione lato client, le credenziali non sono state ancora inviate al server .
  • I listener dell'evento "login" vengono richiamati. Ognuno di questi ora deve inviare una o più richieste all'API RESTful su https://example.com/apiper ottenere i dati specifici dell'utente da visualizzare sulla pagina. Ogni singola richiesta che inviano al servizio Web includerà il nome utente e la password, possibilmente sotto forma di autenticazione HTTP di base , poiché il servizio RESTful non è autorizzato a mantenere lo stato del client da una richiesta all'altra. Poiché il servizio Web è su HTTP sicuro, la password viene crittografata in modo sicuro durante il transito.
  • Il servizio web all'indirizzo https://example.com/apiriceve un sacco di richieste individuali, ognuna con informazioni di autenticazione. Il nome utente e la password in ciascuna richiesta vengono verificati rispetto al database utente e, se trovati corretti, viene eseguita la funzione richiesta e i dati vengono restituiti al client in formato JSON. Se nome utente e password non corrispondono, viene inviato un errore al client sotto forma di un codice di errore HTTP 401.
  • Invece di forzare i client a inviare nome utente e password con ogni richiesta, puoi avere una funzione "get_access_token" nel tuo servizio RESTful che prende nome utente e password e risponde con un token, che è una sorta di hash crittografico che è unico e ha una scadenza data ad esso associata. Questi token sono archiviati nel database con ciascun utente. Quindi il client invia il token di accesso nelle richieste successive. Il token di accesso verrà quindi convalidato rispetto al database anziché al nome utente e alla password.
  • Le applicazioni client non browser come le app telefoniche fanno le stesse delle precedenti, chiedono all'utente di inserire le proprie credenziali, quindi di inviarle (o un token di accesso generato da esse) con ogni richiesta al servizio web.

L'importante punto da togliere da questo esempio è che i servizi Web RESTful richiedono l'autenticazione con ogni richiesta .

Un ulteriore livello di sicurezza in questo scenario aggiungerebbe l'autorizzazione dell'applicazione client oltre all'autenticazione dell'utente. Ad esempio, se si dispone del client Web, delle app iOS e Android che utilizzano tutti il ​​servizio Web, è possibile che il server sappia quale dei tre è il client di una determinata richiesta, indipendentemente da chi sia l'utente autenticato. Ciò può consentire al servizio Web di limitare determinate funzioni a client specifici. Per questo potresti usare chiavi API e segreti, vedi questa risposta per alcune idee a riguardo.

Autenticazione di Facebook

Il flusso di lavoro sopra non funziona per la connessione di Facebook perché l'accesso tramite Facebook ha una terza parte, Facebook stesso. La procedura di accesso richiede che l'utente venga reindirizzato al sito Web di Facebook in cui le credenziali vengono immesse al di fuori del nostro controllo.

Quindi vediamo come cambiano le cose :.

  • L'utente si connette a https://example.com
  • Il server serve una ricca applicazione Javascript che rende la pagina iniziale. Nella pagina è presente un modulo di accesso che include un pulsante "Accedi con Facebook".
  • L'utente fa clic sul pulsante "Accedi con Facebook", che è solo un link che reindirizza a (ad esempio) https://example.com/auth/facebook.
  • Il https://example.com/auth/facebookpercorso è gestito da passport.js (consultare la documentazione )
  • Tutto ciò che l'utente vede è che la pagina cambia e ora si trovano in una pagina ospitata su Facebook dove devono accedere e autorizzare la nostra applicazione web. Questo è completamente al di fuori del nostro controllo.
  • L'utente accede a Facebook e dà l'autorizzazione alla nostra applicazione, quindi Facebook ora reindirizza nuovamente all'URL di richiamata che abbiamo configurato nell'impostazione passport.js, che seguendo l'esempio nella documentazione èhttps://example.com/auth/facebook/callback
  • Il gestore passport.js per il https://example.com/auth/facebook/callbackpercorso richiamerà la funzione di richiamata che riceve il token di accesso a Facebook e alcune informazioni dell'utente da Facebook, incluso l'indirizzo e-mail dell'utente.
  • Con l'e-mail possiamo individuare l'utente nel nostro database e archiviare il token di accesso di Facebook con esso.
  • L'ultima cosa che fai nel callback di Facebook è il reindirizzamento all'applicazione rich client, ma questa volta dobbiamo passare il nome utente e il token di accesso al client in modo che possano utilizzarli. Questo può essere fatto in diversi modi. Ad esempio, le variabili Javascript possono essere aggiunte alla pagina tramite un motore modello lato server, oppure è possibile restituire un cookie con queste informazioni. (grazie a @RyanKimber per aver segnalato i problemi di sicurezza con il passaggio di questi dati nell'URL, come inizialmente suggerito).
  • Quindi ora avviamo l'app a pagina singola ancora una volta, ma il client ha il nome utente e il token di accesso.
  • L'applicazione client può attivare immediatamente l'evento "login" e lasciare che le diverse parti dell'applicazione richiedano le informazioni di cui hanno bisogno dal servizio web.
  • Tutte le richieste inviate https://example.com/apiincluderanno il token di accesso di Facebook per l'autenticazione o il token di accesso dell'applicazione generato dal token di Facebook tramite una funzione "get_access_token" nell'API REST.
  • Le app non browser hanno un po 'più difficile qui, perché OAuth richiede un browser Web per accedere. Per accedere da un'app per telefono o desktop è necessario avviare un browser per eseguire il reindirizzamento a Facebook e, peggio ancora, è necessario che il browser passi il token di accesso di Facebook all'applicazione tramite un meccanismo.

Spero che questo risponda alla maggior parte delle domande. Ovviamente puoi sostituire Facebook con Twitter, Google o qualsiasi altro servizio di autenticazione basato su OAuth.

Sarei interessato a sapere se qualcuno ha un modo più semplice per affrontarlo.


5
Grazie per la tua risposta dettagliata. Solo una domanda: dici così Every single request they send to the web service will include the username and password, eppure dici you can have a "get_access_token" function in your RESTful service. Sembra contraddittorio affermare che REST deve essere apolide, ma l'archiviazione dei token di accesso sul lato server è OK, dal momento che l'atto di archiviazione dei token di accesso significa che il server è ora con stato. Gradirei qualsiasi chiarimento o giustificazione al riguardo. Grazie! :)
ryanrhee,

6
Quando penso al requisito apolide, penso allo stato per una determinata sessione con un client. Non vedo l'archiviazione dei dati associati a un utente in un database, come una password o un token di accesso come "stato", almeno non "stato della sessione". Il tuo server ovviamente deve sapere chi sono gli utenti e le loro password, ma si tratta di dati dell'applicazione che non sono associati a una sessione. Detto questo, molte persone usano i cookie in servizi apparentemente RESTful, quindi quanto rigoroso si desidera aderire alla specifica REST dipende davvero da ciascun implementatore.
Miguel,

1
@Dexter: nel tradizionale caso di accesso un utente inserisce nome utente e password in un modulo e quando preme il pulsante Invia questa informazione viene pubblicata su un server web. In questo caso ciò non accade, l'utente compila il modulo e quando preme Invia un gestore Javascript (un evento onClick nel pulsante di invio) acquisisce i dati e li mantiene nel contesto del client. Non ho un esempio pronto da mostrare, ma attenzione per una seconda parte di questo tutorial sul mio blog in cui mostrerò come è fatto: blog.miguelgrinberg.com/post/…
Miguel

2
Questa è una scrittura ben ponderata, ma contiene un'importante svista o errore. Quando si gestisce l'accesso a Facebook (o Github, Twitter, ecc.), Sarebbe molto preferibile restituire il token al client in un cookie e quindi eliminare il cookie sul lato client una volta scoperto. Passare il token come parte della stringa dell'URL aggiungerà questo URL alla cronologia del browser e (se le cose sono gestite in modo improprio) potrebbe portare alla richiesta di questo URL dal browser. Rende visibile il token. Chiunque abbia quindi copiato l'URL potrebbe falsificare questo utente nella tua applicazione.
Ryan Kimber,

1
@Nathan: l'autent di base su https è sicura, quindi sì, questo è un meccanismo accettabile.
Miguel,

11

Apprezzo molto la spiegazione di @ Miguel con il flusso completo in ogni caso, ma vorrei aggiungerne alcuni sulla parte di autenticazione di Facebook.

Facebook fornisce un SDK Javascript che è possibile utilizzare per ottenere direttamente il token di accesso sul lato client, che viene quindi passato al server e utilizzato per estrarre ulteriormente tutte le informazioni dell'utente da Facebook. Quindi non è necessario alcun reindirizzamento in pratica.

Inoltre, puoi utilizzare lo stesso end-point API anche per le applicazioni mobili. Basta usare l'SDK Android / iOS per Facebook, ottenere l'accesso_token di Facebook sull'estremità del client e passarlo al server.

Per quanto riguarda la natura stateless come spiegato, quando get_access_token viene utilizzato per generare un token e passato al client, anche questo token viene archiviato sul server. Quindi è buono come un token di sessione e credo che questo lo renda stato?

Solo i miei 2 centesimi ..


1
Questo è molto importante, perché in questo modo è possibile eseguire l'autenticazione ajax di una sola pagina utilizzando Facebook. Il token è MOLTO sicuro come autenticazione della sessione, l'unica differenza è che un token può essere passato a un altro dominio e la sessione viene utilizzata solo in un dominio specifico. Quando si utilizzano expressjs e passaporto, è possibile creare un'API che salvi uno stato e utilizzare l'autorizzazione della sessione.
jperelli,

L'API JavaScript è ottimo se si desidera autenticare l'utente per eseguire azioni su Facebook, ma inutile da solo se si desidera convalidare l'utente sul proprio server / database, per quanto posso dire.
James Westgate,

4
È inoltre possibile utilizzare il metodo descritto da Miguel sopra, ma quindi emettere il proprio token JWT come cookie quando si reindirizza il client nel callback di autenticazione. Ciò consente il meglio di entrambi i mondi: l'applicazione a pagina singola può essere preoccupata solo per un tipo di autenticazione (JWT), mantenendo lo stesso livello di sicurezza e fornendo la flessibilità necessaria per supportare qualsiasi accesso social senza utilizzare API JavaScript specifiche per ciascun social network (Facebook, Twitter, LinkedIn, Google, ecc.). Inoltre, consente di mantenere il supporto in stile AJAX per nome utente / password e accesso REST.
Ryan Kimber,

Facebook Javascript SDK al momento non funziona con Chrome iOS. Forse un problema per alcuni.
Demisx

@RyanKimber puoi scrivere un piccolo repository git o simile dove questo è fatto come esempio sono totalmente bloccato
Simon Dragsbæk

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.