Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Estendi l'autenticazione Firebase con il blocco delle funzioni cloud, Estendi l'autenticazione Firebase con il blocco delle funzioni cloud

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

Se hai eseguito l'aggiornamento all'autenticazione Firebase con Identity Platform , puoi estendere l'autenticazione Firebase utilizzando il blocco di Cloud Functions .

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 all'app client.

Prima di iniziare

Per utilizzare le funzioni di blocco, devi aggiornare il tuo progetto Firebase all'autenticazione Firebase con Identity Platform. Se non hai già aggiornato, fallo prima.

Comprensione delle funzioni di blocco

È possibile registrare le funzioni di blocco per due eventi:

  • beforeCreate : si attiva prima che un nuovo utente venga salvato nel database di autenticazione Firebase e prima che un token venga restituito all'app client.

  • beforeSignIn : si attiva dopo che le credenziali di un utente sono state verificate, ma prima che l'autenticazione Firebase restituisca un token ID all'app client. Se la tua app usa 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 .

Tenere presente quanto segue quando si utilizzano le funzioni di blocco:

  • La tua funzione deve rispondere entro 7 secondi. Dopo 7 secondi, l'autenticazione Firebase restituisce un errore e l'operazione del client non riesce.

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

  • Le funzioni si applicano a tutti gli utenti del progetto, inclusi quelli contenuti in un tenant . L'autenticazione Firebase fornisce informazioni sugli utenti alla tua funzione, 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 registrate beforeSignIn .

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

Distribuire e registrare una funzione di blocco

Per inserire il tuo codice personalizzato nei flussi di autenticazione utente, implementa e registra le funzioni di blocco. Dopo aver distribuito e registrato le funzioni di blocco, il codice personalizzato deve essere completato correttamente affinché l'autenticazione e la creazione dell'utente abbiano esito positivo.

Distribuire una funzione di blocco

Distribuisci una funzione di blocco nello stesso modo in cui distribuisci qualsiasi funzione. (per i dettagli, vedere la pagina introduttiva di Cloud Functions). In sintesi:

  1. Scrivi funzioni cloud che gestiscono l'evento beforeCreate , l'evento beforeSignIn o entrambi.

    Ad esempio, per iniziare, puoi aggiungere le seguenti funzioni no-op a index.js :

    const functions = require('firebase-functions');
    
    exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
      // TODO
    });
    
    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      // TODO
    });
    

    Gli esempi precedenti hanno omesso l'implementazione della logica di autenticazione personalizzata. Vedere le sezioni seguenti per informazioni su come implementare le funzioni di blocco e gli scenari comuni per esempi specifici.

  2. Distribuisci le tue funzioni utilizzando l'interfaccia a riga di comando di Firebase:

    firebase deploy --only functions
    

    Devi ridistribuire le tue funzioni ogni volta che le aggiorni.

Registra una funzione di blocco

  1. Vai alla pagina Impostazioni autenticazione Firebase nella console Firebase.

  2. Seleziona la scheda Funzioni di blocco .

  3. Registra la tua funzione di blocco selezionandola dal menu a discesa in Prima della creazione dell'account (prima della creazione) o Prima dell'accesso (prima dell'accesso) .

  4. Salva le modifiche.

Ottenere informazioni sull'utente e sul contesto

Gli eventi beforeSignIn e beforeCreate forniscono oggetti User ed EventContext che contengono informazioni sull'accesso dell'utente. Utilizzare questi valori nel codice per determinare se consentire il proseguimento di un'operazione.

Per un elenco delle proprietà disponibili sull'oggetto User , vedere il riferimento all'API UserRecord .

L'oggetto EventContext contiene le seguenti proprietà:

Nome Descrizione Esempio
locale La localizzazione dell'applicazione. È possibile impostare le impostazioni locali utilizzando l'SDK client o passando l'intestazione delle impostazioni locali nell'API REST. fr o sv-SE
ipAddress L'indirizzo IP del dispositivo da cui l'utente finale si sta registrando o accedendo. 114.14.200.1
userAgent Il programma utente che attiva la funzione di blocco. Mozilla/5.0 (X11; Linux x86_64)
eventId Identificatore univoco dell'evento. rWsyPtolplG2TBFoOkkgyg
eventType Il tipo di evento. Ciò fornisce informazioni sul nome dell'evento, ad esempio beforeSignIn o beforeCreate e sul metodo di accesso associato utilizzato, come Google o email/password. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Sempre USER . USER
resource Il progetto o il tenant di autenticazione Firebase. 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 una registrazione o un tentativo di accesso, lancia un HttpsError nella tua funzione. Per esempio:

Node.js

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

La tabella seguente elenca gli errori che puoi generare, insieme al loro 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 cliente ha specificato un intervallo non valido.
unauthenticated 401 Token OAuth mancante, non valido o scaduto.
permission-denied 403 Il cliente 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-scrittura.
already-exists 409 La risorsa che un client ha tentato di creare esiste già.
resource-exhausted 429 O al di fuori della quota di risorse o al raggiungimento del limite di velocità.
cancelled 499 Richiesta annullata dal cliente.
data-loss 500 Perdita di dati irrecuperabile 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 si trovano all'interno di un dominio specifico di registrarsi per la 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 esegue il wrapping dell'errore e lo restituisce al client come errore interno. Per esempio:

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

La tua app dovrebbe rilevare l'errore e gestirlo di conseguenza. Per 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

Invece di bloccare una registrazione o un tentativo di accesso, puoi consentire all'operazione di continuare, ma modificare l'oggetto User che viene salvato nel database di Firebase Authentication e restituito al client.

Per modificare un utente, restituisci un oggetto dal tuo gestore eventi contenente i campi da modificare. È possibile 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 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 attestazioni token della sessione corrente, ma non vengono mantenute o archiviate nel database.

Ad esempio, se vengono impostate delle attestazioni di sessione, beforeSignIn le restituirà con qualsiasi beforeCreate sessionClaims verranno unite. Quando vengono unite, se una chiave sessionClaims corrisponde a una chiave in customClaims , le customClaims corrispondenti verranno sovrascritte nelle attestazioni token dalla chiave sessionClaims . Tuttavia, la chiave customClaims customClaims verrà comunque mantenuta nel database per richieste future.

Credenziali e dati OAuth supportati

È possibile passare credenziali e dati OAuth a funzioni di blocco da vari provider di identità. La tabella seguente mostra quali credenziali e dati sono supportati per ogni provider di identità:

Provider di identità Token ID Token di accesso Data di scadenza Segreto del token Aggiorna token Reclami 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
Mela No No
SAML No No No No No
OIDC No

Aggiorna i token

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.

I token di aggiornamento non verranno restituiti da alcun provider di identità quando si accede direttamente con una credenziale OAuth, ad esempio un token ID o un token di accesso. In questa situazione, la stessa credenziale OAuth lato client verrà passata alla funzione di blocco.

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

Fornitori OIDC generici

Quando un utente accede con un provider OIDC generico, verranno 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 sono richiesti i seguenti parametri personalizzati:
    • access_type=offline
    • prompt=consent , se l'utente ha precedentemente acconsentito 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);

Ulteriori informazioni sui token di aggiornamento di Google .

Facebook

Quando un utente accede con Facebook, verrà passata la seguente credenziale:

  • 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, verranno passate le seguenti credenziali:

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

Microsoft

Quando un utente accede con Microsoft, verranno passate 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, le seguenti credenziali verranno passate senza parametri o ambiti personalizzati:

  • Token ID
  • Token di accesso
  • Aggiorna token

LinkedIn

Quando un utente accede con LinkedIn, verrà passata la seguente credenziale:

  • Token di accesso

Mela

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

  • Token ID
  • Token di accesso
  • Aggiorna token

Scenari comuni

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

Consentendo solo la registrazione da un dominio specifico

L'esempio seguente mostra come impedire agli utenti che non fanno parte del dominio example.com di registrarsi con la 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 e-mail non verificate

L'esempio seguente mostra come impedire agli utenti con email non verificate di registrarsi con la 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}"`);
  }
});

Richiesta di verifica e-mail al momento della registrazione

L'esempio seguente mostra come richiedere a un utente di verificare la propria posta elettronica 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.`);
  }
});

Trattare alcune email del provider di identità come verificate

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

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

Il seguente esempio di 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 delle attestazioni personalizzate e di sessione

L'esempio seguente mostra come impostare attestazioni personalizzate 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,
      }
    }
  }
});

Tracciamento degli indirizzi IP per monitorare attività sospette

Puoi prevenire il furto di token tracciando l'indirizzo IP da cui un utente effettua l'accesso e confrontandolo con l'indirizzo IP nelle richieste successive. Se la richiesta appare sospetta, ad esempio gli IP provengono da diverse aree geografiche, puoi chiedere all'utente di accedere nuovamente.

  1. Utilizzare le attestazioni di sessione per tenere traccia dell'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 alle risorse che richiedono l'autenticazione con l'autenticazione Firebase, 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!' })
          });
        }
      });
    });
    

Screening delle foto degli utenti

L'esempio seguente mostra come disinfettare le 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 ulteriori informazioni su come rilevare e disinfettare 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 di Google Calendar. Il token di aggiornamento viene archiviato 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();
          });
      });
    })
  }
});
,

Se hai eseguito l'aggiornamento all'autenticazione Firebase con Identity Platform , puoi estendere l'autenticazione Firebase utilizzando il blocco di Cloud Functions .

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 all'app client.

Prima di iniziare

Per utilizzare le funzioni di blocco, devi aggiornare il tuo progetto Firebase all'autenticazione Firebase con Identity Platform. Se non hai già aggiornato, fallo prima.

Comprensione delle funzioni di blocco

È possibile registrare le funzioni di blocco per due eventi:

  • beforeCreate : si attiva prima che un nuovo utente venga salvato nel database di autenticazione Firebase e prima che un token venga restituito all'app client.

  • beforeSignIn : si attiva dopo che le credenziali di un utente sono state verificate, ma prima che l'autenticazione Firebase restituisca un token ID all'app client. Se la tua app usa 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 .

Tenere presente quanto segue quando si utilizzano le funzioni di blocco:

  • La tua funzione deve rispondere entro 7 secondi. Dopo 7 secondi, l'autenticazione Firebase restituisce un errore e l'operazione del client non riesce.

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

  • Le funzioni si applicano a tutti gli utenti del progetto, inclusi quelli contenuti in un tenant . L'autenticazione Firebase fornisce informazioni sugli utenti alla tua funzione, 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 registrate beforeSignIn .

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

Distribuire e registrare una funzione di blocco

Per inserire il tuo codice personalizzato nei flussi di autenticazione utente, implementa e registra le funzioni di blocco. Dopo aver distribuito e registrato le funzioni di blocco, il codice personalizzato deve essere completato correttamente affinché l'autenticazione e la creazione dell'utente abbiano esito positivo.

Distribuire una funzione di blocco

Distribuisci una funzione di blocco nello stesso modo in cui distribuisci qualsiasi funzione. (per i dettagli, vedere la pagina introduttiva di Cloud Functions). In sintesi:

  1. Scrivi funzioni cloud che gestiscono l'evento beforeCreate , l'evento beforeSignIn o entrambi.

    Ad esempio, per iniziare, puoi aggiungere le seguenti funzioni no-op a index.js :

    const functions = require('firebase-functions');
    
    exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
      // TODO
    });
    
    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      // TODO
    });
    

    Gli esempi precedenti hanno omesso l'implementazione della logica di autenticazione personalizzata. Vedere le sezioni seguenti per informazioni su come implementare le funzioni di blocco e gli scenari comuni per esempi specifici.

  2. Distribuisci le tue funzioni utilizzando l'interfaccia a riga di comando di Firebase:

    firebase deploy --only functions
    

    Devi ridistribuire le tue funzioni ogni volta che le aggiorni.

Registra una funzione di blocco

  1. Vai alla pagina Impostazioni autenticazione Firebase nella console Firebase.

  2. Seleziona la scheda Funzioni di blocco .

  3. Registra la tua funzione di blocco selezionandola dal menu a discesa in Prima della creazione dell'account (prima della creazione) o Prima dell'accesso (prima dell'accesso) .

  4. Salva le modifiche.

Ottenere informazioni sull'utente e sul contesto

Gli eventi beforeSignIn e beforeCreate forniscono oggetti User ed EventContext che contengono informazioni sull'accesso dell'utente. Utilizzare questi valori nel codice per determinare se consentire il proseguimento di un'operazione.

Per un elenco delle proprietà disponibili sull'oggetto User , vedere il riferimento all'API UserRecord .

L'oggetto EventContext contiene le seguenti proprietà:

Nome Descrizione Esempio
locale La localizzazione dell'applicazione. È possibile impostare le impostazioni locali utilizzando l'SDK client o passando l'intestazione delle impostazioni locali nell'API REST. fr o sv-SE
ipAddress L'indirizzo IP del dispositivo da cui l'utente finale si sta registrando o accedendo. 114.14.200.1
userAgent Il programma utente che attiva la funzione di blocco. Mozilla/5.0 (X11; Linux x86_64)
eventId Identificatore univoco dell'evento. rWsyPtolplG2TBFoOkkgyg
eventType Il tipo di evento. Ciò fornisce informazioni sul nome dell'evento, ad esempio beforeSignIn o beforeCreate e sul metodo di accesso associato utilizzato, come Google o email/password. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Sempre USER . USER
resource Il progetto o il tenant di autenticazione Firebase. 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 una registrazione o un tentativo di accesso, lancia un HttpsError nella tua funzione. Per esempio:

Node.js

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

La tabella seguente elenca gli errori che puoi generare, insieme al loro 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 cliente ha specificato un intervallo non valido.
unauthenticated 401 Token OAuth mancante, non valido o scaduto.
permission-denied 403 Il cliente 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-scrittura.
already-exists 409 La risorsa che un client ha tentato di creare esiste già.
resource-exhausted 429 O al di fuori della quota di risorse o al raggiungimento del limite di velocità.
cancelled 499 Richiesta annullata dal cliente.
data-loss 500 Perdita di dati irrecuperabile 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 si trovano all'interno di un dominio specifico di registrarsi per la 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 esegue il wrapping dell'errore e lo restituisce al client come errore interno. Per esempio:

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

La tua app dovrebbe rilevare l'errore e gestirlo di conseguenza. Per 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

Invece di bloccare una registrazione o un tentativo di accesso, puoi consentire all'operazione di continuare, ma modificare l'oggetto User che viene salvato nel database di Firebase Authentication e restituito al client.

Per modificare un utente, restituisci un oggetto dal tuo gestore eventi contenente i campi da modificare. È possibile 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 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 attestazioni token della sessione corrente, ma non vengono mantenute o archiviate nel database.

Ad esempio, se vengono impostate delle attestazioni di sessione, beforeSignIn le restituirà con qualsiasi beforeCreate sessionClaims verranno unite. Quando vengono unite, se una chiave sessionClaims corrisponde a una chiave in customClaims , le customClaims corrispondenti verranno sovrascritte nelle attestazioni token dalla chiave sessionClaims . Tuttavia, la chiave customClaims customClaims verrà comunque mantenuta nel database per richieste future.

Credenziali e dati OAuth supportati

È possibile passare credenziali e dati OAuth a funzioni di blocco da vari provider di identità. La tabella seguente mostra quali credenziali e dati sono supportati per ogni provider di identità:

Provider di identità Token ID Token di accesso Data di scadenza Segreto del token Aggiorna token Reclami 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
Mela No No
SAML No No No No No
OIDC No

Aggiorna i token

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.

I token di aggiornamento non verranno restituiti da alcun provider di identità quando si accede direttamente con una credenziale OAuth, ad esempio un token ID o un token di accesso. In questa situazione, la stessa credenziale OAuth lato client verrà passata alla funzione di blocco.

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

Fornitori OIDC generici

Quando un utente accede con un provider OIDC generico, verranno 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 sono richiesti i seguenti parametri personalizzati:
    • access_type=offline
    • prompt=consent , se l'utente ha precedentemente acconsentito 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);

Ulteriori informazioni sui token di aggiornamento di Google .

Facebook

Quando un utente accede con Facebook, verrà passata la seguente credenziale:

  • 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, verranno passate le seguenti credenziali:

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

Microsoft

Quando un utente accede con Microsoft, verranno passate 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, le seguenti credenziali verranno passate senza parametri o ambiti personalizzati:

  • Token ID
  • Token di accesso
  • Aggiorna token

LinkedIn

Quando un utente accede con LinkedIn, verrà passata la seguente credenziale:

  • Token di accesso

Mela

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

  • Token ID
  • Token di accesso
  • Aggiorna token

Scenari comuni

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

Consentendo solo la registrazione da un dominio specifico

L'esempio seguente mostra come impedire agli utenti che non fanno parte del dominio example.com di registrarsi con la 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 e-mail non verificate

L'esempio seguente mostra come impedire agli utenti con email non verificate di registrarsi con la 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}"`);
  }
});

Richiesta di verifica e-mail al momento della registrazione

L'esempio seguente mostra come richiedere a un utente di verificare la propria posta elettronica 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.`);
  }
});

Trattare alcune email del provider di identità come verificate

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

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

Il seguente esempio di 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 delle attestazioni personalizzate e di sessione

L'esempio seguente mostra come impostare attestazioni personalizzate 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,
      }
    }
  }
});

Tracciamento degli indirizzi IP per monitorare attività sospette

Puoi prevenire il furto di token tracciando l'indirizzo IP da cui un utente effettua l'accesso e confrontandolo con l'indirizzo IP nelle richieste successive. Se la richiesta appare sospetta, ad esempio gli IP provengono da diverse aree geografiche, puoi chiedere all'utente di accedere nuovamente.

  1. Utilizzare le attestazioni di sessione per tenere traccia dell'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 alle risorse che richiedono l'autenticazione con l'autenticazione Firebase, 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!' })
          });
        }
      });
    });
    

Screening delle foto degli utenti

L'esempio seguente mostra come disinfettare le 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 ulteriori informazioni su come rilevare e disinfettare 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 di Google Calendar. Il token di aggiornamento viene archiviato 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();
          });
      });
    })
  }
});