Autenticazione degli utenti con Node.js


Le app in esecuzione sulle piattaforme gestite da Google Cloud come App Engine possono evitare di gestire l'autenticazione degli utenti e la gestione delle sessioni utilizzando Identity-Aware Proxy (IAP) per controllare l'accesso alla loro. IAP non solo può controllare l'accesso all'app, ma anche fornire informazioni sugli utenti autenticati, inclusi l'indirizzo email e un identificatore persistente all'app sotto forma di nuove intestazioni HTTP.

Obiettivi

  • Richiedi agli utenti della tua app App Engine di autenticarsi utilizzando IAP.

  • Accedi alle identità degli utenti nell'app per visualizzare l'indirizzo email autenticato dell'utente.

Costi

In questo documento utilizzi i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi basata sull'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud potrebbero essere idonei per una prova gratuita.

Al termine delle attività descritte in questo documento, puoi evitare la fatturazione continua eliminando le risorse che hai creato. Per ulteriori informazioni, consulta la sezione Pulizia.

Prima di iniziare

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Install the Google Cloud CLI.
  4. To initialize the gcloud CLI, run the following command:

    gcloud init
  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  6. Install the Google Cloud CLI.
  7. To initialize the gcloud CLI, run the following command:

    gcloud init

Premesse

Questo tutorial utilizza IAP per autenticare gli utenti. Questo è solo uno dei diversi approcci possibili. Per scoprire di più sui vari metodi per l'autenticazione degli utenti, consulta la sezione Concetti di autenticazione.

App Hello user-email-address

L'app di questo tutorial è un'applicazione minima di App Engine in Hello World, con una funzionalità non tipica: al posto di "Hello World", mostra "Hello user-email-address", dove user-email-address è l'indirizzo email dell'utente autenticato.

Questa funzionalità è possibile esaminando le informazioni autenticate che IAP aggiunge a ogni richiesta web trasmessa all'app. Esistono tre nuove intestazioni di richiesta aggiunte a ogni richiesta web che raggiunge l'app. Le prime due intestazioni sono stringhe di testo normale che puoi utilizzare per identificare l'utente. La terza intestazione è un oggetto firmato tramite crittografia con le stesse informazioni.

  • X-Goog-Authenticated-User-Email: l'indirizzo email di un utente lo identifica. Non memorizzare informazioni personali se la tua app può evitarle. Questa app non memorizza alcun dato, ma lo fa eco all'utente.

  • X-Goog-Authenticated-User-Id: questo ID utente assegnato da Google non mostra le informazioni relative all'utente, ma consente all'app di sapere che un utente che ha eseguito l'accesso è lo stesso visualizzato in precedenza.

  • X-Goog-Iap-Jwt-Assertion: puoi configurare le app Google Cloud in modo che accetti richieste web da altre app cloud, ignorando IAP e le richieste web Internet. Se un'app è così configurata, è possibile che tali richieste abbiano intestazioni falsificate. Anziché utilizzare una delle intestazioni di testo normale menzionate in precedenza, puoi utilizzare e verificare questa intestazione firmata crittograficamente per verificare che le informazioni siano state fornite da Google. Nell'intestazione firmata sono disponibili sia l'indirizzo email dell'utente sia un ID utente permanente.

Se hai la certezza che l'app sia configurata in modo che possa essere raggiunta solo dalle richieste web Internet e che nessuno possa disattivare il servizio IAP per l'app, il recupero di un ID utente univoco richiede una sola riga di codice:

userId = req.header('X-Goog-Authenticated-User-ID') :? null;

Tuttavia, un'app resiliente dovrebbe presentare problemi, inclusi problemi ambientali o di configurazione imprevisti, quindi consigliamo di creare una funzione che utilizzi e verifichi l'intestazione con firma crittografica. La firma di quell'intestazione non può essere falsificata e, una volta verificata, può essere utilizzata per restituire l'identificazione.

Crea il codice sorgente

  1. Utilizza un editor di testo per creare un file denominato app.js e incolla il seguente codice al suo interno:

    const express = require('express');
    const metadata = require('gcp-metadata');
    const {OAuth2Client} = require('google-auth-library');
    
    const app = express();
    const oAuth2Client = new OAuth2Client();
    
    // Cache externally fetched information for future invocations
    let aud;
    
    async function audience() {
      if (!aud && (await metadata.isAvailable())) {
        let project_number = await metadata.project('numeric-project-id');
        let project_id = await metadata.project('project-id');
    
        aud = '/projects/' + project_number + '/apps/' + project_id;
      }
    
      return aud;
    }
    
    async function validateAssertion(assertion) {
      if (!assertion) {
        return {};
      }
    
      // Check that the assertion's audience matches ours
      const aud = await audience();
    
      // Fetch the current certificates and verify the signature on the assertion
      const response = await oAuth2Client.getIapPublicKeys();
      const ticket = await oAuth2Client.verifySignedJwtWithCertsAsync(
        assertion,
        response.pubkeys,
        aud,
        ['https://cloud.google.com/iap']
      );
      const payload = ticket.getPayload();
    
      // Return the two relevant pieces of information
      return {
        email: payload.email,
        sub: payload.sub,
      };
    }
    
    app.get('/', async (req, res) => {
      const assertion = req.header('X-Goog-IAP-JWT-Assertion');
      let email = 'None';
      try {
        const info = await validateAssertion(assertion);
        email = info.email;
      } catch (error) {
        console.log(error);
      }
      res.status(200).send(`Hello ${email}`).end();
    });
    
    // Start the server
    const PORT = process.env.PORT || 8080;
    app.listen(PORT, () => {
      console.log(`App listening on port ${PORT}`);
      console.log('Press Ctrl+C to quit.');
    });
    

    Questo file app.js è descritto in dettaglio nella sezione Informazioni sul codice più avanti di questo tutorial.

  2. Crea un altro file denominato package.json e incolla al suo interno quanto segue:

    {
      "name": "iap-authentication",
      "description": "Minimal app to use authentication information from IAP.",
      "private": true,
      "license": "Apache-2.0",
      "author": "Google LLC",
      "repository": {
        "type": "git",
        "url": "https://github.com/GoogleCloudPlatform/getting-started-nodejs.git"
      },
      "engines": {
        "node": ">=12.0.0"
      },
      "scripts": {
        "start": "node app.js",
        "test": "mocha --exit test/*.test.js"
      },
      "dependencies": {
        "express": "^4.17.1",
        "gcp-metadata": "^5.0.0",
        "google-auth-library": "^8.0.0"
      },
      "devDependencies": {
        "mocha": "^9.0.0",
        "supertest": "^6.0.0"
      }
    }
    

    Il file package.json elenca tutte le dipendenze Node.js necessarie per la tua app. jsonwebtoken offre la funzione di controllo e decodifica JWT.

  3. Crea un file denominato app.yaml e inserisci il seguente testo al suo interno:

    # Copyright 2019 Google LLC
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #    http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    runtime: nodejs10
    

    Il file app.yaml indica ad App Engine quale ambiente della lingua richiede il tuo codice.

Nozioni di base sul codice

Questa sezione spiega come funziona il codice nel file app.js. Se vuoi eseguire l'app, puoi andare direttamente alla sezione Esegui il deployment dell'app.

Il seguente codice è presente nel file app.js. Quando l'app riceve un HTTP GET, viene richiamata la richiesta di trasferimento per /:

La funzione ottiene il valore di intestazione JWT dell'asserzione che IAP ha aggiunto dalla richiesta in entrata e chiama una funzione per convalidare il valore della firma con crittografia. Il primo valore restituito (indirizzo email) viene quindi utilizzato in una pagina web minima che viene creata e restituita.

app.get('/', async (req, res) => {
  const assertion = req.header('X-Goog-IAP-JWT-Assertion');
  let email = 'None';
  try {
    const info = await validateAssertion(assertion);
    email = info.email;
  } catch (error) {
    console.log(error);
  }
  res.status(200).send(`Hello ${email}`).end();
});

La funzione validateAssertion utilizza la funzione verifySignedJwtWithCertsAsync() di google-auth-library per verificare che la dichiarazione sia stata firmata correttamente e per estrarre le informazioni del payload dall'asserzione. Si tratta dell'indirizzo email dell'utente autenticato e di un ID univoco permanente per l'utente. Se l'asserzione non può essere decodificata, questa funzione genera e stampa un messaggio per registrare l'errore.

La convalida di un'asserzione JWT richiede la conoscenza dei certificati di chiave pubblica dell'entità che ha firmato la dichiarazione (Google in questo caso) e il pubblico a cui è destinata. Per un'app App Engine, il pubblico è costituito da una stringa contenente informazioni di identificazione del progetto Google Cloud. Questa funzione riceve i certificati e la stringa di pubblico dalle funzioni che lo precedono.

async function validateAssertion(assertion) {
  if (!assertion) {
    return {};
  }

  // Check that the assertion's audience matches ours
  const aud = await audience();

  // Fetch the current certificates and verify the signature on the assertion
  const response = await oAuth2Client.getIapPublicKeys();
  const ticket = await oAuth2Client.verifySignedJwtWithCertsAsync(
    assertion,
    response.pubkeys,
    aud,
    ['https://cloud.google.com/iap']
  );
  const payload = ticket.getPayload();

  // Return the two relevant pieces of information
  return {
    email: payload.email,
    sub: payload.sub,
  };
}

Puoi cercare l'ID numerico e il nome del progetto Google Cloud e inserirli autonomamente nel codice sorgente, ma la funzione audience lo fa eseguendo una query sul servizio di metadati standard reso disponibile a ogni app App Engine. Poiché il servizio metadati è esterno al codice dell'app, il risultato viene salvato in una variabile globale restituita senza dover cercare i metadati nelle chiamate successive.

async function audience() {
  if (!aud && (await metadata.isAvailable())) {
    let project_number = await metadata.project('numeric-project-id');
    let project_id = await metadata.project('project-id');

    aud = '/projects/' + project_number + '/apps/' + project_id;
  }

  return aud;
}

Il servizio di metadati di App Engine (e servizi di metadati simili per altri servizi di computing di Google Cloud) sembra un sito web ed è oggetto di query da parte di query web standard. Tuttavia, il servizio di metadati non è in realtà un sito esterno, ma una funzionalità interna che restituisce informazioni richieste sull'app in esecuzione, pertanto è sicuro utilizzare http invece delle richieste https. Viene utilizzato per ottenere gli identificatori Google Cloud attuali necessari per definire il pubblico di destinazione dell'asserzione JWT.

const response = await oAuth2Client.getIapPublicKeys();

La verifica di una firma digitale richiede il certificato di chiave pubblica del firmatario. Google fornisce un sito web che restituisce tutti i certificati di chiave pubblica attualmente utilizzati. Questi risultati vengono memorizzati nella cache nel caso in cui siano nuovamente necessari nella stessa istanza dell'app.

Deployment dell'app

Ora puoi eseguire il deployment dell'app e abilitare IAP per richiedere agli utenti di autenticarsi prima che possano accedere all'app.

  1. Nella finestra del terminale, vai alla directory contenente il file app.yaml ed esegui il deployment dell'app in App Engine:

    gcloud app deploy
    
  2. Quando richiesto, seleziona un'area geografica nelle vicinanze.

  3. Quando ti viene chiesto se vuoi continuare l'operazione di deployment, inserisci Y.

    Entro pochi minuti, la tua app è attiva su Internet.

  4. Visualizza l'app:

    gcloud app browse
    

    Nell'output, copia web-site-url, l'indirizzo web dell'app.

  5. In web-site-url, apri l'app in una finestra del browser.

    Nessuna email viene visualizzata perché non stai ancora utilizzando IAP, quindi non vengono inviate informazioni sull'utente all'app.

Abilita IAP

Ora che esiste un'istanza di App Engine, puoi proteggerla con IAP:

  1. Nella console Google Cloud, vai alla pagina Identity-Aware Proxy.

    Vai alla pagina Identity-Aware Proxy

  2. Poiché è la prima volta che attivi un'opzione di autenticazione per questo progetto, viene visualizzato un messaggio che ti chiede di configurare la schermata per il consenso OAuth prima di poter utilizzare IAP.

    Fai clic su Configure Consent Screen (Configura schermata per consenso).

  3. Nella scheda Schermata Consenso OAuth della pagina Credenziali, completa i seguenti campi:

    • Se il tuo account è in un'organizzazione Google Workspace, seleziona Esterno e fai clic su Crea. Per iniziare, l'app sarà disponibile soltanto agli utenti che consenti esplicitamente.

    • Nel campo Nome applicazione, inserisci IAP Example.

    • Inserisci il tuo indirizzo email nel campo Email dell'assistenza.

    • Nel campo Dominio autorizzato, inserisci la parte del nome host dell'URL dell'app, ad esempio iap-example-999999.uc.r.appspot.com. Premi il tasto Enter dopo aver inserito il nome host nel campo.

    • Nel campo Link alla home page dell'applicazione, inserisci l'URL della tua app, ad esempio https://iap-example-999999.uc.r.appspot.com/.

    • Nel campo Riga per le norme sulla privacy dell'applicazione, utilizza lo stesso URL del link alla home page a scopo di test.

  4. Fai clic su Salva. Quando ti viene chiesto di creare le credenziali, puoi chiudere la finestra.

  5. Nella console Google Cloud, vai alla pagina Identity-Aware Proxy.

    Vai alla pagina Identity-Aware Proxy

  6. Per aggiornare la pagina, fai clic su Aggiorna . Nella pagina viene visualizzato un elenco di risorse che puoi proteggere.

  7. Nella colonna IAP, fai clic per attivare IAP per l'app.

  8. Nel browser, vai di nuovo a web-site-url.

  9. Per l'autenticazione devi usare una schermata di accesso anziché la pagina web. Quando accedi, ti viene negato l'accesso perché IAP non dispone di un elenco di utenti a cui permettere l'accesso all'app.

Aggiungi utenti autorizzati all'app

  1. Nella console Google Cloud, vai alla pagina Identity-Aware Proxy.

    Vai alla pagina Identity-Aware Proxy

  2. Seleziona la casella di controllo per l'app App Engine e fai clic su Aggiungi entità.

  3. Inserisci allAuthenticatedUsers e seleziona il ruolo Utente applicazione web con protezione IAP/IAP.

  4. Fai clic su Salva.

Ora qualsiasi utente che Google può autenticare può accedere all'app. Se vuoi, puoi limitare ulteriormente l'accesso aggiungendo solo una o più persone o gruppi come entità:

  • Qualsiasi indirizzo email Gmail o Google Workspace

  • Indirizzo email di un gruppo Google

  • Un nome di dominio di Google Workspace

Accedi all'app

  1. Nel browser, vai a web-site-url.

  2. Per aggiornare la pagina, fai clic su Aggiorna .

  3. Nella schermata di accesso, accedi con le tue credenziali Google.

    Viene visualizzata la pagina "Hello user-email-address" con il tuo indirizzo email.

    Se visualizzi ancora la stessa pagina di prima, potrebbe esserci un problema con il browser che non aggiorna completamente le nuove richieste ora che hai attivato IAP. Chiudi tutte le finestre del browser, riaprile e riprova.

Concetti di autenticazione

Esistono diversi modi in cui un'app può autenticare i propri utenti e limitare l'accesso solo agli utenti autorizzati. Nelle sezioni seguenti sono elencati i metodi di autenticazione più comuni, che consentono di ridurre il livello di impegno per l'app.

Opzione Vantaggi Svantaggi
Autenticazione app
  • L'app può essere eseguita su qualsiasi piattaforma, con o senza connessione a Internet
  • Gli utenti non devono utilizzare nessun altro servizio per gestire l'autenticazione
  • L'app deve gestire le credenziali utente in modo sicuro, per evitare che vengano divulgate
  • L'app deve conservare i dati delle sessioni per gli utenti che hanno eseguito l'accesso
  • L'app deve fornire la registrazione utente, le modifiche alle password e il recupero della password
OAuth2
  • L'app può essere eseguita su qualsiasi piattaforma connessa a Internet, inclusa una workstation per gli sviluppatori
  • L'app non richiede funzionalità quali la registrazione dell'utente, la modifica della password o il recupero delle password.
  • Il rischio di divulgazione di informazioni sugli utenti è delegato a un altro servizio
  • Nuove misure di sicurezza per l'accesso gestite all'esterno dell'app
  • Gli utenti devono registrarsi al servizio di identità
  • L'app deve conservare i dati delle sessioni per gli utenti che hanno eseguito l'accesso
IAP
  • L'app non richiede alcun codice per gestire gli utenti, l'autenticazione o lo stato della sessione
  • L'app non ha credenziali utente che potrebbero essere state violate
  • L'app può essere eseguita soltanto sulle piattaforme supportate dal servizio. In particolare, alcuni servizi Google Cloud che supportano IAP, come App Engine.

Autenticazione gestita dall'app

Con questo metodo, l'app gestisce autonomamente ogni aspetto dell'autenticazione utente. L'app deve mantenere il proprio database di credenziali utente e gestire le sessioni utente e deve fornire funzioni per gestire gli account utente e le password, controllare le credenziali utente, nonché emettere, controllare e aggiornare le sessioni utente a ogni accesso autenticato. Il seguente diagramma illustra il metodo di autenticazione gestito dall'app.

Flusso gestito dall'applicazione

Come mostrato nel diagramma, dopo che l'utente ha eseguito l'accesso, l'app crea e gestisce le informazioni sulla sessione dell'utente. Quando l'utente invia una richiesta all'app, quest'ultima deve includere le informazioni sulla sessione che l'app è responsabile della verifica.

Il vantaggio principale di questo approccio è che è indipendente e sotto il controllo dell'app. Inoltre, l'app non deve necessariamente essere disponibile su Internet. Lo svantaggio principale è che l'app è ora responsabile di fornire tutte le funzionalità di gestione degli account e di proteggere tutti i dati sensibili delle credenziali.

Autenticazione esterna con OAuth2

Un'alternativa valida per gestire tutto all'interno dell'app è usare un servizio di identità esterno, come Google, che gestisce tutte le informazioni e le funzionalità dell'account utente ed è responsabile della salvaguardia delle credenziali sensibili. Quando un utente tenta di accedere all'app, la richiesta viene reindirizzata al servizio di identità, che autentica l'utente, quindi reindirizza la richiesta all'app con le informazioni di autenticazione necessarie. Per saperne di più, consulta Utilizzo di OAuth 2.0 per applicazioni server web.

Il seguente diagramma illustra l'autenticazione esterna con il metodo OAuth2.

Flusso OAuth2

Il flusso nel diagramma inizia quando l'utente invia una richiesta di accesso all'app. Invece di rispondere direttamente, l'app reindirizza il browser dell'utente alla piattaforma dell'identità di Google, che mostra una pagina per accedere a Google. Dopo l'accesso, il browser dell'utente viene reindirizzato all'app. Questa richiesta include informazioni che l'app può utilizzare per cercare informazioni sull'utente ora autenticato e che l'app risponde ora all'utente.

Questo metodo presenta molti vantaggi per l'app: delega tutte le funzionalità di gestione dell'account e i rischi al servizio esterno, il che può migliorare l'accesso e la sicurezza dell'account senza che l'app debba essere modificata. Tuttavia, come mostrato nel diagramma precedente, l'app deve avere accesso a Internet per poter utilizzare questo metodo. L'app è anche responsabile della gestione delle sessioni dopo che l'utente è stato autenticato.

Identity-Aware Proxy

Il terzo approccio, che tratta di questo tutorial, consiste nell'utilizzare IAP per gestire tutta l'autenticazione e la gestione delle sessioni con eventuali modifiche all'app. IAP intercetta tutte le richieste web alla tua app, blocca tutte quelle non autenticate e le altre passa attraverso i dati sull'identità degli utenti aggiunti a ogni richiesta.

La gestione delle richieste è mostrata nel diagramma seguente.

Flusso IAP

Le richieste degli utenti vengono intercettate da IAP, che blocca le richieste non autenticate. Le richieste autenticate vengono trasferite all'app, a condizione che l'utente autenticato sia nell'elenco degli utenti consentiti. Alle richieste trasmesse tramite IAP vengono aggiunte intestazioni che identificano l'utente che ha effettuato la richiesta.

L'app non deve più gestire le informazioni dell'account utente o della sessione. Qualsiasi operazione che richieda la conoscenza di un identificatore univoco per l'utente può recuperarla direttamente da ogni richiesta web in entrata. Tuttavia, questo può essere utilizzato solo per i servizi di computing che supportano IAP, ad esempio App Engine e bilanciatori del carico. Non puoi utilizzare IAP su una macchina di sviluppo locale.

Esegui la pulizia

Per evitare che al tuo Account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.