Trigger di blocco dell'autenticazione (1ª gen.)

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 a un utente di autenticarsi se non soddisfa determinati criteri o aggiornare le informazioni di un utente prima di restituirle alla tua app client.

Prima di iniziare

Per utilizzare le funzioni di blocco, devi eseguire l'upgrade del tuo progetto Firebase a Firebase Authentication with Identity Platform. Se non hai ancora eseguito l'upgrade, fallo ora.

Informazioni sulle funzioni di blocco

Puoi registrare funzioni di blocco per i seguenti eventi:

  • beforeCreate: si attiva prima che un nuovo utente venga salvato nel database Firebase Authentication e prima che un token venga restituito alla tua app client.

  • beforeSignIn: si attiva dopo la verifica delle credenziali di un utente, ma prima che Firebase Authentication restituisca un token ID alla tua 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.

  • beforeEmail (solo Node.js): si attiva prima che un'email (ad esempio,
    un'email di accesso o di reimpostazione password) venga inviata a un utente.

  • beforeSms (solo Node.js): si attiva prima che un messaggio SMS venga inviato a un utente, ad esempio per l'autenticazione a più fattori.

Quando utilizzi le funzioni di blocco, tieni presente quanto segue:

  • La funzione deve rispondere entro 7 secondi. Dopo 7 secondi, Firebase Authentication restituisce un errore e l'operazione del 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 tutti gli errori che la funzione può restituire.

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

  • Il collegamento di un altro provider di identità a un account attiva nuovamente tutte le funzioni beforeSignIn registrate.

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

Esegui il deployment di una funzione di blocco

Per inserire il tuo codice personalizzato nei flussi di autenticazione utente, esegui il deployment delle funzioni di blocco. Una volta eseguito il deployment delle funzioni di blocco, il codice personalizzato deve essere completato correttamente affinché l'autenticazione e la creazione degli utenti vadano a buon fine.

Il deployment di una funzione di blocco viene eseguito nello stesso modo di qualsiasi altra funzione. (per maggiori dettagli, consulta la pagina Inizia di Cloud Functions). In sintesi:

  1. Scrivi una funzione che gestisca l'evento target.

    Ad esempio, per iniziare, puoi aggiungere una funzione no-op come la seguente a index.js:

    const functions = require('firebase-functions/v1');
    
    exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
      // TODO
    });
    
    The above example has omitted the implementation of custom auth logic. See
    the following sections to learn how to implement your blocking functions and
    [Common scenarios](#common-scenarios) for specific examples.
    
  1. Esegui il deployment delle funzioni utilizzando l'interfaccia a riga di comando Firebase:

    firebase deploy --only functions
    

    Devi eseguire nuovamente il deployment delle funzioni ogni volta che le aggiorni.

Recupero delle 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 o meno l'esecuzione di un'operazione.

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 Le impostazioni internazionali 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 esegue la registrazione o l'accesso. 114.14.200.1
userAgent Lo 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. In questo modo, vengono fornite 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 Firebase Authentication. 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

Blocco della registrazione o dell'accesso

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

Node.js

throw new functions.auth.HttpsError('permission-denied');

La tabella seguente elenca gli errori che puoi generare, insieme al 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 Quota di risorse esaurita o limite di frequenza raggiunto.
cancelled 499 La richiesta è stata annullata dal client.
data-loss 500 Perdita di dati non recuperabili o danneggiamento dei dati.
unknown 500 Errore del server sconosciuto.
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 functions.auth.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

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  // (If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from context.resource, e.g. 'projects/project-id/tenant/tenant-id-1')

  // Only users of a specific domain can sign up.
  if (user.email.indexOf('@acme.com') === -1) {
    throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Indipendentemente dal fatto che utilizzi un messaggio predefinito o personalizzato, Cloud Functions aggrega l'errore e lo restituisce al client come errore interno. Ad esempio:

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

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.
    }
  });

Modifica di un utente

Anziché bloccare un tentativo di registrazione o accesso, puoi consentire la continuazione dell'operazione, ma modificare l'oggetto User salvato nel database di Firebase Authentication 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 Firebase Authentication, 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 = functions.auth.user().beforeCreate((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. Solo per sessionClaims, vengono propagate alle rivendicazioni dei token della sessione corrente, ma non vengono rese persistenti o archiviate nel database.

Ad esempio, se sono impostati sessionClaims, beforeSignIn li restituirà con eventuali claim 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 verrà comunque mantenuta nel database per le richieste future.

Credenziali e dati OAuth supportati

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

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

Token di aggiornamento

Per utilizzare un token di aggiornamento in una funzione di blocco, devi prima selezionare la casella di controllo nella pagina Funzioni di blocco della console Firebase (vai a Sicurezza > Autenticazione > scheda Impostazioni).

I token di aggiornamento non verranno restituiti da nessun provider di identità quando accedi direttamente con una credenziale OAuth, ad esempio un token ID o un token di accesso. In questo caso, alla funzione di blocco verrà passata la stessa credenziale OAuth lato client.

Le sezioni seguenti descrivono i tipi di provider di identità e le relative 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 è attualmente supportato solo tramite 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 precedentemente dato il consenso 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, viene passata la seguente credenziale:

  • 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: viene 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, verranno 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
  • Token di accesso
  • Token di aggiornamento

Scenari comuni

Gli esempi seguenti mostrano alcuni casi d'uso comuni per le funzioni di blocco:

Consentire la registrazione solo da un dominio specifico

L'esempio seguente mostra come impedire agli utenti che non fanno parte del dominio example.com di registrarsi alla tua app:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (!user.email || user.email.indexOf('@example.com') === -1) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Blocco della registrazione degli utenti con indirizzi email non verificati

L'esempio seguente mostra come impedire agli utenti con indirizzi email non verificati di registrarsi alla tua app:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unverified email "${user.email}"`);
  }
});

Richiedere la verifica email al momento della registrazione

L'esempio seguente mostra come richiedere a un utente di verificare il proprio indirizzo email dopo la registrazione:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  const locale = context.locale;
  if (user.email && !user.emailVerified) {
    // Send custom email verification on sign-up.
    return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
      return sendCustomVerificationEmail(user.email, link, locale);
    });
  }
});

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
 if (user.email && !user.emailVerified) {
   throw new functions.auth.HttpsError(
     'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
  }
});

Trattamento di determinate email del provider di identità come verificate

L'esempio seguente mostra come considerare verificate le email degli utenti di determinati provider di identità:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
    return {
      emailVerified: true,
    };
  }
});

Blocco dell'accesso da determinati indirizzi IP

L'esempio seguente mostra come bloccare l'accesso da determinati intervalli di indirizzi IP:

Node.js

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
  if (isSuspiciousIpAddress(context.ipAddress)) {
    throw new functions.auth.HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});

Impostazione di attestazioni personalizzate e di sessione

L'esempio seguente mostra come impostare i claim personalizzati e di sessione:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'saml.my-provider-id') {
    return {
      // Employee ID does not change so save in persistent claims (stored in
      // Auth DB).
      customClaims: {
        eid: context.credential.claims.employeeid,
      },
      // Copy role and groups to token claims. These will not be persisted.
      sessionClaims: {
        role: context.credential.claims.role,
        groups: context.credential.claims.groups,
      }
    }
  }
});

Monitoraggio degli indirizzi IP per rilevare attività sospette

Puoi impedire il furto di token monitorando l'indirizzo IP da cui un utente accede e confrontandolo con l'indirizzo IP delle richieste successive. Se la richiesta sembra sospetta, ad esempio se gli IP provengono da regioni geografiche diverse, puoi chiedere all'utente di eseguire nuovamente l'accesso.

  1. Utilizza le attestazioni di sessione per monitorare l'indirizzo IP con cui l'utente accede:

    Node.js

    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      return {
        sessionClaims: {
          signInIpAddress: context.ipAddress,
        },
      };
    });
    
  2. Quando un utente tenta di accedere a risorse che richiedono l'autenticazione con Firebase Authentication, confronta l'indirizzo IP nella richiesta con l'IP utilizzato per accedere:

    Node.js

    app.post('/getRestrictedData', (req, res) => {
      // Get the ID token passed.
      const idToken = req.body.idToken;
      // Verify the ID token, check if revoked and decode its payload.
      admin.auth().verifyIdToken(idToken, true).then((claims) => {
        // Get request IP address
        const requestIpAddress = req.connection.remoteAddress;
        // Get sign-in IP address.
        const signInIpAddress = claims.signInIpAddress;
        // Check if the request IP address origin is suspicious relative to
        // the session IP addresses. The current request timestamp and the
        // auth_time of the ID token can provide additional signals of abuse,
        // especially if the IP address suddenly changed. If there was a sudden
        // geographical change in a short period of time, then it will give
        // stronger signals of possible abuse.
        if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) {
          // Suspicious IP address change. Require re-authentication.
          // You can also revoke all user sessions by calling:
          // admin.auth().revokeRefreshTokens(claims.sub).
          res.status(401).send({error: 'Unauthorized access. Please login again!'});
        } else {
          // Access is valid. Try to return data.
          getData(claims).then(data => {
            res.end(JSON.stringify(data);
          }, error => {
            res.status(500).send({ error: 'Server error!' })
          });
        }
      });
    });
    

Scansione delle foto degli utenti

L'esempio seguente mostra come eseguire la sanitizzazione delle foto del profilo degli utenti:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.photoURL) {
    return isPhotoAppropriate(user.photoURL)
      .then((status) => {
        if (!status) {
          // Sanitize inappropriate photos by replacing them with guest photos.
          // Users could also be blocked from sign-up, disabled, etc.
          return {
            photoUrl: PLACEHOLDER_GUEST_PHOTO_URL,
          };
        }
      });
});

Per scoprire di più su come rilevare e sanitizzare le immagini, consulta la documentazione di Cloud Vision.

Accesso alle credenziali OAuth del provider di identità di un utente

L'esempio seguente mostra come ottenere un token di aggiornamento per un utente che ha eseguito l'accesso con Google e utilizzarlo per chiamare le API Google Calendar. Il token di aggiornamento viene memorizzato per l'accesso offline.

Node.js

const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
  keys.web.client_id,
  keys.web.client_secret
);

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'google.com') {
    // Store the refresh token for later offline use.
    // These will only be returned if refresh tokens credentials are included
    // (enabled by Cloud console).
    return saveUserRefreshToken(
        user.uid,
        context.credential.refreshToken,
        'google.com'
      )
      .then(() => {
        // Blocking the function is not required. The function can resolve while
        // this operation continues to run in the background.
        return new Promise((resolve, reject) => {
          // For this operation to succeed, the appropriate OAuth scope should be requested
          // on sign in with Google, client-side. In this case:
          // https://www.googleapis.com/auth/calendar
          // You can check granted_scopes from within:
          // context.additionalUserInfo.profile.granted_scopes (space joined list of scopes).

          // Set access token/refresh token.
          oAuth2Client.setCredentials({
            access_token: context.credential.accessToken,
            refresh_token: context.credential.refreshToken,
          });
          const calendar = google.calendar('v3');
          // Setup Onboarding event on user's calendar.
          const event = {/** ... */};
          calendar.events.insert({
            auth: oauth2client,
            calendarId: 'primary',
            resource: event,
          }, (err, event) => {
            // Do not fail. This is a best effort approach.
            resolve();
          });
      });
    })
  }
});

Override del verdetto di reCAPTCHA Enterprise per l'operazione dell'utente

L'esempio seguente mostra come eseguire l'override di un verdetto reCAPTCHA Enterprise per i flussi utente supportati.

Consulta Abilita reCAPTCHA Enterprise per scoprire di più sull'integrazione di reCAPTCHA Enterprise con Firebase Authentication.

Le funzioni di blocco possono essere utilizzate per consentire o bloccare i flussi in base a fattori personalizzati, eseguendo l'override del risultato fornito da reCAPTCHA Enterprise.

Node.js

const functions = require("firebase-functions/v1");
exports.beforesmsv1 = functions.auth.user().beforeSms((context) => {
 if (
   context.smsType === "SIGN_IN_OR_SIGN_UP" &&
   context.additionalUserInfo.phoneNumber.includes('+91')
 ) {
   return {
     recaptchaActionOverride: "ALLOW",
   };
 }

 // Allow users to sign in with recaptcha score greater than 0.5
 if (event.additionalUserInfo.recaptchaScore > 0.5) {
   return {
     recaptchaActionOverride: 'ALLOW',
   };
 }

 // Block all others.
 return  {
   recaptchaActionOverride: 'BLOCK',
 }
});