Code, Learn

Autenticazione semplificata tramite Passport

3 Giugno 2020 - 3 minuti di lettura

Oggi molte applicazioni permettono di autenticarsi e/o consentire l’accesso ad alcuni dati dell’utente tramite le API esposte da terze parti come Google, Facebook, Twitter, GitHub e molti altri.

Una di queste è Passport, che ho imparato ad apprezzare per la sua semplicità e affidabilità, oggetto di questo articolo contenente un esempio d’uso.

Buona lettura.

Introduzione

Passport (1) è un middleware per l’autenticazione e l’autorizzazione in applicazioni Node.js che permette di gestire in maniera uniforme queste funzionalità sfruttando molteplici meccanismi e servizi.

Passport incapsula e astrae la specifica implementazione di una modalità di autenticazione in un modulo detto Strategy; questi moduli possono essere installati singolarmente e utilizzate attraverso un’interfaccia comune. Esistono Strategy per effettuare l’autenticazione/autorizzazione tramite i più noti social network e piattaforme ma è anche possibile crearne di nuove.

Configurazione di una Strategy

Disclaimer

Prima di utilizzare una Strategy è sempre bene controllare che venga costantemente mantenuta per supportare la versione più recente delle API con cui si interfaccia. È possibile infatti imbattersi in Strategy non più funzionanti, ad esempio il package passport-github non è più compatibile con GitHub dopo l’ultimo upgrade alla versione 3.0 delle API ed è quindi stato deprecato in favore di passport-github2 (2).

Per usare una Strategy è prima necessario configurarla utilizzando la funzione use. Per fare un esempio configuriamo la Strategy per effettuare l’autenticazione tramite le API di GitHub (OAuth 2.0 API) nel modo seguente:

var GitHubStrategy = require('passport-github2').Strategy;
passport.use(new GitHubStrategy({
   clientID: GITHUB_CLIENT_ID,
   clientSecret: GITHUB_CLIENT_SECRET,
   callbackURL: "auth/github/callback"
  },
  function(accessToken, refreshToken, profile, done) {
    // qui implementiamo la logica di gestione dell’utente
    // tipicamente verifichiamo se l’utente esiste già e, se non esiste, lo creiamo
    // infine chiamiamo la funzione done
    // ad esempio, se stiamo utilizzando MongoDB con Mongoose:
    User.findOrCreate({ githubId: profile.id }, function (err, user) {
      if (err)
         return done(err);
      return done(null, user);
    });
  }
));

I valori di clientID e clientSecret identificano la nostra applicazione e si ottengono registrando l’applicazione da autorizzare attraverso il relativo form sul sito web di GitHub (3) (tutte le piattaforme che si basano su OAuth 2.0 presentano una modalità di registrazione analoga).

Il valore di callbackURL invece indica a quale URL l’utente viene reindirizzato uno volta che la procedura di autenticazione è andata a buon fine.

Autenticazione delle request

La funzione authenticate autentica la request indicando la Strategy da utilizzare. Ad esempio, in un’applicazione Express (dopo aver inizializzato Passport (4) ) la funzione può essere utilizzata come middleware di route nel modo seguente:

// endpoint per effettuare l’autenticazione
// il parametro scope permette di indicare i dati che si vogliono rendere accessibili alla nostra applicazione
app.get('/auth/github',
  passport.authenticate('github', { scope: [ 'user:email' ] }));

// endpoint a cui si viene reindirizzati dopo che l'autenticazione è andata a buon fine
app.get('/auth/github/callback',
  passport.authenticate('github',
  { failureRedirect: '/login' }), // redirect in caso di autenticazione fallita
  function(req, res) {     
    // l’autenticazione è stata eseguita con successo
    res.redirect('/private-route);  
  });

Gestione delle sessioni

In seguito ad un’autenticazione avvenuta con successo, ogni request conterrà il cookie relativo alla sessione appena creata. Passport permette di serializzare e deserializzare l’utente della sessione tramite apposite funzioni.

Per serializzare l’utente si utilizza la funzione serializeUser:

// serializziamo l'id dell’utente all'interno della sessione
passport.serializeUser(function(user, done) {
  done(null, user.id);
});

Per ottenere l’utente precedentemente serializzato si utilizza la funzione deserializeUser:

// otteniamo l’utente della sessione tramite il suo id
// ad esempio, se stiamo utilizzando MongoDB con Mongoose:
passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

Quando l’utente è stato ottenuto con successo, le relative informazioni vengono incluse nella request in req.user.

Per le modalità di autenticazione che non necessitano di una sessione, è possibile disabilitarne la gestione tramite il parametro session del middleware:

…
passport.authenticate(‘strategy-without-session’, { session: false })
…

Conclusioni

Passport è molto utile per implementare funzionalità di autenticazione/autorizzazione nella propria app in modo semplice e veloce; purtroppo però non include alcune funzionalità fondamentali per un’applicazione web enterprise, come il reset della password (necessario nel caso si utilizzino Strategy personalizzate) e l’encrypt delle credenziali.

Articolo scritto da