Join us in person and online for Firebase Summit on October 18, 2022. Learn how Firebase can help you accelerate app development, release your app with confidence, and scale with ease. Register now

Étendre l'authentification Firebase en bloquant les fonctions cloud

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Si vous avez effectué la mise à niveau vers Firebase Authentication avec Identity Platform , vous pouvez étendre Firebase Authentication à l'aide de Cloud Functions bloquantes .

Les fonctions de blocage vous permettent d'exécuter un code personnalisé qui modifie le résultat de l'enregistrement ou de la connexion d'un utilisateur à votre application. Par exemple, vous pouvez empêcher un utilisateur de s'authentifier s'il ne répond pas à certains critères ou mettre à jour les informations d'un utilisateur avant de les renvoyer à votre application cliente.

Avant que tu commences

Pour utiliser les fonctions de blocage, vous devez mettre à niveau votre projet Firebase vers Firebase Authentication with Identity Platform. Si vous ne l'avez pas déjà fait, faites-le d'abord.

Comprendre les fonctions de blocage

Vous pouvez enregistrer des fonctions de blocage pour deux événements :

  • beforeCreate : se déclenche avant qu'un nouvel utilisateur ne soit enregistré dans la base de données Firebase Authentication et avant qu'un jeton ne soit renvoyé à votre application cliente.

  • beforeSignIn : se déclenche après la vérification des informations d'identification d'un utilisateur, mais avant que Firebase Authentication ne renvoie un jeton d'identification à votre application cliente. Si votre application utilise l'authentification multifacteur, la fonction se déclenche une fois que l'utilisateur a vérifié son deuxième facteur. Notez que la création d'un nouvel utilisateur déclenche également beforeSignIn , en plus de beforeCreate .

Gardez ce qui suit à l'esprit lorsque vous utilisez les fonctions de blocage :

  • Votre fonction doit répondre dans les 7 secondes. Après 7 secondes, Firebase Authentication renvoie une erreur et l'opération client échoue.

  • Les codes de réponse HTTP autres que 200 sont transmis à vos applications clientes. Assurez-vous que votre code client gère toutes les erreurs que votre fonction peut renvoyer.

  • Les fonctions s'appliquent à tous les utilisateurs de votre projet, y compris ceux contenus dans un locataire . Firebase Authentication fournit des informations sur les utilisateurs de votre fonction, y compris les locataires auxquels ils appartiennent, afin que vous puissiez répondre en conséquence.

  • Lier un autre fournisseur d'identité à un compte relance toutes les fonctions beforeSignIn enregistrées.

  • L'authentification anonyme et personnalisée ne déclenche pas de fonctions de blocage.

Déployer et enregistrer une fonction de blocage

Pour insérer votre code personnalisé dans les flux d'authentification des utilisateurs, déployez et enregistrez les fonctions de blocage. Une fois vos fonctions de blocage déployées et enregistrées, votre code personnalisé doit se terminer avec succès pour que l'authentification et la création d'utilisateur réussissent.

Déployer une fonction de blocage

Vous déployez une fonction de blocage de la même manière que vous déployez n'importe quelle fonction. (voir la page de démarrage de Cloud Functions pour plus de détails). En résumé:

  1. Écrivez des fonctions Cloud qui gèrent l'événement beforeCreate , l'événement beforeSignIn ou les deux.

    Par exemple, pour commencer, vous pouvez ajouter les fonctions no-op suivantes à 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
    });
    

    Les exemples ci-dessus ont omis l'implémentation de la logique d'authentification personnalisée. Consultez les sections suivantes pour savoir comment implémenter vos fonctions de blocage et Scénarios courants pour des exemples spécifiques.

  2. Déployez vos fonctions à l'aide de la CLI Firebase :

    firebase deploy --only functions
    

    Vous devez redéployer vos fonctions chaque fois que vous les mettez à jour.

Enregistrer une fonction de blocage

  1. Accédez à la page Paramètres d'authentification Firebase dans la console Firebase.

  2. Sélectionnez l'onglet Fonctions de blocage .

  3. Enregistrez votre fonction de blocage en la sélectionnant dans le menu déroulant sous Avant la création du compte (beforeCreate) ou Avant la connexion (beforeSignIn) .

  4. Enregistrez vos modifications.

Obtenir des informations sur l'utilisateur et le contexte

Les événements beforeSignIn et beforeCreate fournissent des objets User et EventContext qui contiennent des informations sur la connexion de l'utilisateur. Utilisez ces valeurs dans votre code pour déterminer s'il faut autoriser la poursuite d'une opération.

Pour obtenir la liste des propriétés disponibles sur l'objet User , consultez la référence de l' API UserRecord .

L'objet EventContext contient les propriétés suivantes :

Nom La description Exemple
locale Les paramètres régionaux de l'application. Vous pouvez définir les paramètres régionaux à l'aide du SDK client ou en transmettant l'en-tête de paramètres régionaux dans l'API REST. fr ou sv-SE
ipAddress L'adresse IP de l'appareil à partir duquel l'utilisateur final s'enregistre ou se connecte. 114.14.200.1
userAgent L'agent utilisateur déclenchant la fonction de blocage. Mozilla/5.0 (X11; Linux x86_64)
eventId L'identifiant unique de l'événement. rWsyPtolplG2TBFoOkkgyg
eventType Le type d'événement. Cela fournit des informations sur le nom de l'événement, comme beforeSignIn ou beforeCreate , et la méthode de connexion associée utilisée, comme Google ou e-mail/mot de passe. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Toujours USER . USER
resource Le projet ou locataire Firebase Authentication. projects/ project-id /tenants/ tenant-id
timestamp L'heure à laquelle l'événement a été déclenché, formatée en tant que chaîne RFC 3339 . Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Un objet contenant des informations sur l'utilisateur. AdditionalUserInfo
credential Un objet contenant des informations sur les informations d'identification de l'utilisateur. AuthCredential

Blocage de l'inscription ou de la connexion

Pour bloquer une tentative d'inscription ou de connexion, lancez une HttpsError dans votre fonction. Par exemple:

Node.js

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

Le tableau suivant répertorie les erreurs que vous pouvez générer, ainsi que leur message d'erreur par défaut :

Nom Code Message
invalid-argument 400 Le client a spécifié un argument non valide.
failed-precondition 400 La demande ne peut pas être exécutée dans l'état actuel du système.
out-of-range 400 Le client a spécifié une plage non valide.
unauthenticated 401 Jeton OAuth manquant, non valide ou arrivé à expiration.
permission-denied 403 Le client n'a pas l'autorisation suffisante.
not-found 404 La ressource spécifiée est introuvable.
aborted 409 Conflit de concurrence, tel qu'un conflit de lecture-modification-écriture.
already-exists 409 La ressource qu'un client a tenté de créer existe déjà.
resource-exhausted 429 Soit en dehors du quota de ressources, soit en atteignant la limitation de débit.
cancelled 499 Demande annulée par le client.
data-loss 500 Perte de données irrécupérable ou corruption de données.
unknown 500 Erreur de serveur inconnue.
internal 500 Erreur interne du serveur.
not-implemented 501 Méthode API non implémentée par le serveur.
unavailable 503 Service indisponible.
deadline-exceeded 504 Délai de demande dépassé.

Vous pouvez également spécifier un message d'erreur personnalisé :

Node.js

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

L'exemple suivant montre comment empêcher les utilisateurs qui ne font pas partie d'un domaine spécifique de s'inscrire à votre application :

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}"`);
  }
});

Que vous utilisiez un message par défaut ou personnalisé, Cloud Functions encapsule l'erreur et la renvoie au client en tant qu'erreur interne. Par exemple:

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

Votre application doit détecter l'erreur et la gérer en conséquence. Par exemple:

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

Modification d'un utilisateur

Au lieu de bloquer une tentative d'enregistrement ou de connexion, vous pouvez autoriser la poursuite de l'opération, mais modifier l'objet User qui est enregistré dans la base de données de Firebase Authentication et renvoyé au client.

Pour modifier un utilisateur, renvoyez un objet de votre gestionnaire d'événement contenant les champs à modifier. Vous pouvez modifier les champs suivants :

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

À l'exception de sessionClaims , tous les champs modifiés sont enregistrés dans la base de données de Firebase Authentication, ce qui signifie qu'ils sont inclus dans le jeton de réponse et persistent entre les sessions utilisateur.

L'exemple suivant montre comment définir un nom d'affichage par défaut :

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

Si vous enregistrez un gestionnaire d'événements pour beforeCreate et beforeSignIn , notez que beforeSignIn s'exécute après beforeCreate . Les champs utilisateur mis à jour dans beforeCreate sont visibles dans beforeSignIn . Si vous définissez un champ autre que sessionClaims dans les deux gestionnaires d'événements, la valeur définie dans beforeSignIn écrase la valeur définie dans beforeCreate . Pour sessionClaims uniquement, ils sont propagés aux revendications de jeton de la session en cours, mais ne sont pas conservés ou stockés dans la base de données.

Par exemple, si des sessionClaims sont définies, beforeSignIn les renverra avec toutes les revendications beforeCreate et elles seront fusionnées. Lorsqu'ils sont fusionnés, si une clé sessionClaims correspond à une clé dans customClaims , les customClaims correspondants seront écrasés dans les revendications de jeton par la clé sessionClaims . Cependant, la clé customClaims sera toujours conservée dans la base de données pour les demandes futures.

Identifiants et données OAuth pris en charge

Vous pouvez transmettre les informations d'identification et les données OAuth aux fonctions de blocage de divers fournisseurs d'identité. Le tableau suivant indique les informations d'identification et les données prises en charge pour chaque fournisseur d'identité :

Fournisseur d'identité Jeton d'identification Jeton d'accès Date d'expiration Jeton secret Actualiser le jeton Revendications de connexion
Google Oui Oui Oui Non Oui Non
Facebook Non Oui Oui Non Non Non
Twitter Non Oui Non Oui Non Non
GitHub Non Oui Non Non Non Non
Microsoft Oui Oui Oui Non Oui Non
LinkedIn Non Oui Oui Non Non Non
Yahoo Oui Oui Oui Non Oui Non
Pomme Oui Oui Oui Non Oui Non
SAML Non Non Non Non Non Oui
OIDC Oui Oui Oui Non Oui Oui

Actualiser les jetons

Pour utiliser un jeton d'actualisation dans une fonction de blocage, vous devez d'abord cocher la case sur la page Fonctions de blocage de la console Firebase.

Les jetons d'actualisation ne seront renvoyés par aucun fournisseur d'identité lors de la connexion directe avec un identifiant OAuth, tel qu'un jeton d'identification ou un jeton d'accès. Dans cette situation, les mêmes informations d'identification OAuth côté client seront transmises à la fonction de blocage.

Les sections suivantes décrivent chaque type de fournisseur d'identité et leurs informations d'identification et données prises en charge.

Fournisseurs OIDC génériques

Lorsqu'un utilisateur se connecte avec un fournisseur OIDC générique, les informations d'identification suivantes sont transmises :

  • ID token : Fourni si le flux id_token est sélectionné.
  • Jeton d'accès : Fourni si le flux de code est sélectionné. Notez que le flux de code n'est actuellement pris en charge que via l'API REST.
  • Jeton d'actualisation : fourni si la portée offline_access est sélectionnée.

Exemple:

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

Google

Lorsqu'un utilisateur se connecte à Google, les informations d'identification suivantes sont transmises :

  • Jeton d'identification
  • Jeton d'accès
  • Jeton d'actualisation : fourni uniquement si les paramètres personnalisés suivants sont demandés :
    • access_type=offline
    • prompt=consent , si l'utilisateur a déjà consenti et qu'aucune nouvelle étendue n'a été demandée

Exemple:

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

En savoir plus sur les jetons d'actualisation Google .

Facebook

Lorsqu'un utilisateur se connecte avec Facebook, les informations d'identification suivantes sont transmises :

  • Jeton d' accès : Un jeton d'accès est retourné qui peut être échangé contre un autre jeton d'accès. En savoir plus sur les différents types de jetons d'accès pris en charge par Facebook et comment vous pouvez les échanger contre des jetons à longue durée de vie .

GitHub

Lorsqu'un utilisateur se connecte à GitHub, les informations d'identification suivantes sont transmises :

  • Jeton d'accès : n'expire que s'il est révoqué.

Microsoft

Lorsqu'un utilisateur se connecte à Microsoft, les informations d'identification suivantes sont transmises :

  • Jeton d'identification
  • Jeton d'accès
  • Jeton d'actualisation : passé à la fonction de blocage si la portée offline_access est sélectionnée.

Exemple:

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

Yahoo

Lorsqu'un utilisateur se connecte à Yahoo, les informations d'identification suivantes sont transmises sans paramètres ni champs d'application personnalisés :

  • Jeton d'identification
  • Jeton d'accès
  • Actualiser le jeton

LinkedIn

Lorsqu'un utilisateur se connecte avec LinkedIn, les informations d'identification suivantes sont transmises :

  • Jeton d'accès

Pomme

Lorsqu'un utilisateur se connecte à Apple, les informations d'identification suivantes sont transmises sans paramètres ni portées personnalisés :

  • Jeton d'identification
  • Jeton d'accès
  • Actualiser le jeton

Scénarios courants

Les exemples suivants illustrent certains cas d'utilisation courants pour les fonctions de blocage :

Autoriser uniquement l'enregistrement à partir d'un domaine spécifique

L'exemple suivant montre comment empêcher les utilisateurs qui ne font pas partie du domaine example.com de s'enregistrer auprès de votre application :

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}"`);
  }
});

Empêcher les utilisateurs avec des e-mails non vérifiés de s'inscrire

L'exemple suivant montre comment empêcher les utilisateurs dont les e-mails ne sont pas vérifiés de s'inscrire à votre application :

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}"`);
  }
});

Exiger une vérification de l'e-mail lors de l'inscription

L'exemple suivant montre comment demander à un utilisateur de vérifier son adresse e-mail après son inscription :

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

Traiter certains e-mails de fournisseur d'identité comme vérifiés

L'exemple suivant montre comment traiter les e-mails des utilisateurs de certains fournisseurs d'identité comme vérifiés :

Node.js

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

Bloquer la connexion à partir de certaines adresses IP

L'exemple suivant montre comment bloquer la connexion à partir de certaines plages d'adresses IP :

Node.js

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

Définition des revendications personnalisées et de session

L'exemple suivant montre comment définir des revendications personnalisées et de session :

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

Suivi des adresses IP pour surveiller les activités suspectes

Vous pouvez empêcher le vol de jeton en suivant l'adresse IP à partir de laquelle un utilisateur se connecte et en la comparant à l'adresse IP lors des demandes ultérieures. Si la demande semble suspecte - par exemple, les adresses IP proviennent de différentes régions géographiques - vous pouvez demander à l'utilisateur de se reconnecter.

  1. Utilisez les revendications de session pour suivre l'adresse IP avec laquelle l'utilisateur se connecte :

    Node.js

    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      return {
        sessionClaims: {
          signInIpAddress: context.ipAddress,
        },
      };
    });
    
  2. Lorsqu'un utilisateur tente d'accéder à des ressources nécessitant une authentification avec Firebase Authentication, comparez l'adresse IP de la requête avec l'adresse IP utilisée pour se connecter :

    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!' })
          });
        }
      });
    });
    

Filtrer les photos des utilisateurs

L'exemple suivant montre comment nettoyer les photos de profil des utilisateurs :

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

Pour en savoir plus sur la détection et le nettoyage des images, consultez la documentation Cloud Vision .

Accéder aux informations d'identification OAuth du fournisseur d'identité d'un utilisateur

L'exemple suivant montre comment obtenir un jeton d'actualisation pour un utilisateur qui s'est connecté avec Google et l'utiliser pour appeler les API Google Agenda. Le jeton d'actualisation est stocké pour un accès hors ligne.

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