Personalizzare il flusso di autenticazione utilizzando le funzioni di blocco

Questo documento mostra come estendere l'autenticazione di Identity Platform utilizzando le funzioni Cloud Run di blocco.

Le funzioni di blocco ti consentono di eseguire codice personalizzato che modifica il risultato della registrazione o dell'accesso di un utente alla tua app. Ad esempio, puoi impedire l'autenticazione di un utente se non soddisfa determinati criteri o aggiornare le informazioni di un utente prima di restituirle all'app client.

Prima di iniziare

Crea un'app con Identity Platform. Per istruzioni, vedi Guida rapida.

Informazioni sulle funzioni di blocco

Puoi registrare le funzioni di blocco per due eventi:

  • beforeCreate: viene attivato prima che un nuovo utente venga salvato nel database Identity Platform e prima che un token venga restituito all'app client.

  • beforeSignIn: si attiva dopo la verifica delle credenziali di un utente, ma prima che Identity Platform restituisca un token ID all'app client. Se la tua app utilizza l'autenticazione a più fattori, la funzione si attiva dopo che l'utente ha verificato il secondo fattore. Tieni presente che la creazione di un nuovo utente attiva anche beforeSignIn, oltre a beforeCreate.

Tieni presente quanto segue quando utilizzi le funzioni di blocco:

  • La funzione deve rispondere entro sette secondi. Dopo sette secondi, Identity Platform restituisce un errore e l'operazione client non va a buon fine.

  • I codici di risposta HTTP diversi da 200 vengono passati alle tue app client. Assicurati che il codice client gestisca eventuali errori che la funzione può restituire.

  • Le funzioni si applicano a tutti gli utenti del progetto, inclusi quelli contenuti in un tenant. Identity Platform fornisce alla tua funzione informazioni sugli utenti, inclusi eventuali tenant a cui appartengono, in modo che tu possa rispondere di conseguenza.

  • Il collegamento di un altro provider di identità a un account riattiva tutte le funzioni beforeSignIn registrate. Non include i fornitori di email e password.

  • L'autenticazione anonima e personalizzata non supporta le funzioni di blocco.

  • Se utilizzi anche funzioni asincrone, l'oggetto utente ricevuto da una funzione asincrona non contiene gli aggiornamenti della funzione di blocco.

Creare una funzione di blocco

I passaggi seguenti mostrano come creare una funzione di blocco:

  1. Vai alla pagina Impostazioni di Identity Platform nella consoleGoogle Cloud .

    Vai alla pagina Impostazioni

  2. Seleziona la scheda Trigger.

  3. A seconda di quando vuoi attivare la funzione di blocco, seleziona Crea funzione dal menu a discesa Prima di creare (beforeCreate) o dal menu a discesa Prima dell'accesso (beforeSignIn).

  4. Nel riquadro Crea funzione, procedi nel seguente modo:

    1. Nel campo Nome funzione, inserisci un nome per la funzione.

    2. Nell'elenco Regione, seleziona una regione.

    3. Copia l'URL del trigger.

    4. Nella sezione Impostazioni di runtime, build, connessioni e sicurezza, vai a ogni scheda e configura le impostazioni richieste.

  5. Nella sezione Codice sorgente, crea il codice sorgente.

    1. Nell'elenco Runtime, seleziona il runtime.

    2. Nel campo Entry point, inserisci l'entry point del codice.

    3. Apri index.js utilizzando l'editor in linea. Elimina il codice di esempio helloWorld e sostituiscilo con uno dei seguenti:

      Per rispondere alla registrazione:

      import * as gcipCloudFunctions from 'gcip-cloud-functions';
      
      const authClient = new gcipCloudFunctions.Auth();
      
      exports.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
      });
      

      Per rispondere all'accesso:

      import * as gcipCloudFunctions from 'gcip-cloud-functions';
      
      const authClient = new gcipCloudFunctions.Auth();
      
      exports.beforeSignIn = authClient.functions().beforeSignInHandler((user, context) => {
      
      });
      
    4. Apri package.json e aggiungi il seguente blocco di dipendenze: Per la versione più recente dell'SDK, consulta gcip-cloud-functions.

      {
        "type": "module",
        "name": ...,
        "version": ...,
      
        "dependencies": {
          "gcip-cloud-functions": "^0.2.0"
        }
      }
      
  6. Per pubblicare la funzione, fai clic su Esegui il deployment della funzione.

  7. Seleziona le credenziali del token OAuth da trasmettere alla funzione.

  8. Nella pagina Impostazioni, fai clic su Salva.

Puoi anche creare e gestire le funzioni utilizzando Google Cloud CLI o l'API REST. Per istruzioni dettagliate, consulta la documentazione di Cloud Run Functions.

Ottenere informazioni sull'utente e sul contesto

Gli eventi beforeSignIn e beforeCreate forniscono oggetti User e EventContext che contengono informazioni sull'utente che esegue l'accesso. Utilizza questi valori nel codice per determinare se consentire a un'operazione di procedere.

Per un elenco delle proprietà disponibili nell'oggetto User, consulta il riferimento API UserRecord.

L'oggetto EventContext contiene le seguenti proprietà:

Nome Descrizione Esempio
locale La lingua dell'applicazione. Puoi impostare le impostazioni internazionali utilizzando l'SDK client o passando l'intestazione delle impostazioni internazionali nell'API REST. fr o sv-SE
ipAddress L'indirizzo IP del dispositivo da cui l'utente finale si registra o esegue l'accesso. 114.14.200.1
userAgent L'user agent che attiva la funzione di blocco. Mozilla/5.0 (X11; Linux x86_64)
eventId L'identificatore univoco dell'evento. rWsyPtolplG2TBFoOkkgyg
eventType Il tipo di evento. Fornisce informazioni sul nome dell'evento, ad esempio beforeSignIn o beforeCreate, e sul metodo di accesso associato utilizzato, ad esempio Google o email/password. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Sempre USER. USER
resource Il progetto o il tenant Identity Platform. projects/project-id/tenants/tenant-id
timestamp L'ora in cui è stato attivato l'evento, formattata come stringa RFC 3339. Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Un oggetto contenente informazioni sull'utente. AdditionalUserInfo
credential Un oggetto contenente informazioni sulle credenziali dell'utente. AuthCredential

Bloccare la registrazione o l'accesso

Per bloccare un tentativo di registrazione o accesso, genera un HttpsError nella tua funzione. Ad esempio:

Node.js

throw new gcipCloudFunctions.https.HttpsError('permission-denied');

La tabella seguente elenca gli errori che puoi generare, insieme al relativo messaggio di errore predefinito:

Nome Codice Messaggio
invalid-argument 400 Il client ha specificato un argomento non valido.
failed-precondition 400 La richiesta non può essere eseguita nello stato attuale del sistema.
out-of-range 400 Il client ha specificato un intervallo non valido.
unauthenticated 401 Token OAuth mancante, non valido o scaduto.
permission-denied 403 Il client non dispone di autorizzazioni sufficienti.
not-found 404 La risorsa specificata non è stata trovata.
aborted 409 Conflitto di concorrenza, ad esempio un conflitto di lettura, modifica e scrittura.
already-exists 409 La risorsa che un client ha cercato di creare esiste già.
resource-exhausted 429 Questi errori si verificano quando la quota di risorse è esaurita o si sta raggiungendo limitazione di frequenza.
cancelled 499 La richiesta è stata annullata dal client.
data-loss 500 Perdita di dati non recuperabili o danneggiamento dei dati.
unknown 500 Errore sconosciuto del server.
internal 500 Errore interno del server.
not-implemented 501 Metodo API non implementato dal server.
unavailable 503 Servizio non disponibile.
deadline-exceeded 504 Scadenza richiesta superata.

Puoi anche specificare un messaggio di errore personalizzato:

Node.js

throw new gcipCloudFunctions.https.HttpsError('permission-denied', 'Unauthorized request origin!');

L'esempio seguente mostra come impedire agli utenti che non appartengono a un dominio specifico di registrarsi alla tua app:

Node.js

// Import the Cloud Auth Admin module.
import * as gcipCloudFunctions from 'gcip-cloud-functions';
// Initialize the Auth client.
const authClient = new gcipCloudFunctions.Auth();
// Http trigger with Cloud Run functions.
exports.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
  // If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from context.resource, eg. 'projects/project-id/tenant/tenant-id-1'
  // Only users of a specific domain can sign up.
  if (!user.email.endsWith('@acme.com')) {
    throw new gcipCloudFunctions.https.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Indipendentemente dal fatto che utilizzi un messaggio predefinito o personalizzato, Cloud Run Functions esegue il wrapping dell'errore e lo restituisce al client come errore interno. Ad esempio, se generi il seguente errore nella tua funzione:

throw new gcipCloudFunctions.https.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);

Alla tua app client viene restituito un errore simile al seguente (se utilizzi l'SDK client, l'errore viene incluso come errore interno):

{
  "error": {
    "code": 400,
    "message": "BLOCKING_FUNCTION_ERROR_RESPONSE : HTTP Cloud Function returned an error. Code: 400, Status: \"INVALID_ARGUMENT\", Message: \"Unauthorized email user@evil.com\"",
    "errors": [
      {
        "message": "BLOCKING_FUNCTION_ERROR_RESPONSE : HTTP Cloud Function returned an error. Code: 400, Status: \"INVALID_ARGUMENT\", Message: \"Unauthorized email user@evil.com\"",
        "domain": "global",
        "reason": "invalid"
      }
    ]
  }
}

La tua app deve rilevare l'errore e gestirlo di conseguenza. Ad esempio:

JavaScript

// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
firebase.auth().createUserWithEmailAndPassword('johndoe@example.com', 'password')
  .then((result) => {
    result.user.getIdTokenResult()
  })
  .then((idTokenResult) => {
    console.log(idTokenResult.claim.admin);
  })
  .catch((error) => {
    if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
      // Display error.
    } else {
      // Registration succeeds.
    }
  });

Modificare un utente

Anziché bloccare un tentativo di registrazione o accesso, puoi consentire il proseguimento dell'operazione, ma modificare l'oggetto User salvato nel database di Identity Platform e restituito al client.

Per modificare un utente, restituisci un oggetto dal gestore di eventi contenente i campi da modificare. Puoi modificare i seguenti campi:

  • displayName
  • disabled
  • emailVerified
  • photoURL
  • customClaims
  • sessionClaims (solo beforeSignIn)

Ad eccezione di sessionClaims, tutti i campi modificati vengono salvati nel database di Identity Platform, il che significa che sono inclusi nel token di risposta e persistono tra le sessioni utente.

L'esempio seguente mostra come impostare un nome visualizzato predefinito:

Node.js

exports.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
  return {
    // If no display name is provided, set it to "guest".
    displayName: user.displayName || 'guest'
  };
});

Se registri un gestore di eventi sia per beforeCreate che per beforeSignIn, tieni presente che beforeSignIn viene eseguito dopo beforeCreate. I campi utente aggiornati in beforeCreate sono visibili in beforeSignIn. Se imposti un campo diverso da sessionClaims in entrambi i gestori di eventi, il valore impostato in beforeSignIn sovrascrive il valore impostato in beforeCreate. Per sessionClaims, vengono propagate alle rivendicazioni del token della sessione corrente, ma non vengono mantenute o memorizzate nel database.

Ad esempio, se sono impostati sessionClaims, beforeSignIn li restituirà con eventuali rivendicazioni beforeCreate e verranno uniti. Quando vengono uniti, se una chiave sessionClaims corrisponde a una chiave in customClaims, la chiave customClaims corrispondente verrà sovrascritta nelle rivendicazioni del token dalla chiave sessionClaims. Tuttavia, la chiave customClaims sovrascritta viene comunque mantenuta nel database per le richieste future.

Credenziali e dati OAuth supportati

Puoi trasmettere le credenziali e i dati OAuth alle funzioni di blocco di vari provider di identità. La tabella seguente mostra le credenziali e i dati supportati per ciascun provider di identità:

Provider di identità Token ID Token di accesso Data di scadenza Secret del token Aggiorna token Attestazioni di accesso
Google No No
Facebook No No No No
Twitter No No No No
GitHub No No No No No
Microsoft No No
LinkedIn No No No No
Yahoo No No
Apple No No
SAML No No No No No
OIDC No

Token di aggiornamento

Per utilizzare un token di aggiornamento in una funzione di blocco, devi prima selezionare la casella di controllo nella sezione Trigger del menu a discesa Includi credenziali token nella console Google Cloud .

I token di aggiornamento non verranno restituiti da alcun provider di identità quando accedi direttamente con una credenziale OAuth, come un token ID o un token di accesso. In questo caso, alla funzione di blocco verrà trasmessa la stessa credenziale OAuth lato client. Tuttavia, per i flussi a tre passaggi, un token di aggiornamento potrebbe essere disponibile se il provider di identità lo supporta.

Le sezioni seguenti descrivono ogni tipo di provider di identità e le credenziali e i dati supportati.

Provider OIDC generici

Quando un utente accede con un provider OIDC generico, vengono trasmesse le seguenti credenziali:

  • Token ID: fornito se è selezionato il flusso id_token.
  • Token di accesso: fornito se è selezionato il flusso di codice. Tieni presente che il flusso di codice è supportato solo con l'API REST.
  • Token di aggiornamento: fornito se è selezionato l'ambito offline_access.

Esempio:

const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Google

Quando un utente accede con Google, vengono trasmesse le seguenti credenziali:

  • Token ID
  • Token di accesso
  • Token di aggiornamento: fornito solo se vengono richiesti i seguenti parametri personalizzati:
    • access_type=offline
    • prompt=consent, se l'utente ha dato il consenso in precedenza e non è stato richiesto un nuovo ambito

Esempio:

const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
  'access_type': 'offline',
  'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);

Scopri di più sui token di aggiornamento di Google.

Facebook

Quando un utente accede con Facebook, vengono trasmesse le seguenti credenziali:

  • Token di accesso: viene restituito un token di accesso che può essere scambiato con un altro token di accesso. Scopri di più sui diversi tipi di token di accesso supportati da Facebook e su come scambiarli con token di lunga durata.

GitHub

Quando un utente accede con GitHub, vengono trasmesse le seguenti credenziali:

  • Token di accesso: non scade a meno che non venga revocato.

Microsoft

Quando un utente accede con Microsoft, vengono trasmesse le seguenti credenziali:

  • Token ID
  • Token di accesso
  • Token di aggiornamento: passato alla funzione di blocco se è selezionato l'ambito offline_access.

Esempio:

const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Yahoo

Quando un utente accede con Yahoo, vengono trasmesse le seguenti credenziali senza parametri o ambiti personalizzati:

  • Token ID
  • Token di accesso
  • Token di aggiornamento

LinkedIn

Quando un utente accede con LinkedIn, vengono trasmesse le seguenti credenziali:

  • Token di accesso

Apple

Quando un utente accede con Apple, vengono trasmesse le seguenti credenziali senza parametri o ambiti personalizzati:

  • Token ID