Les fonctions de blocage vous permettent d'exécuter du code personnalisé qui modifie le résultat de l'inscription 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 client.
Avant que tu commences
Pour utiliser les fonctions de blocage, vous devez mettre à niveau votre projet Firebase vers Firebase Authentication avec Identity Platform. Si vous n'avez pas encore effectué la mise à niveau, 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 d'authentification Firebase et avant qu'un jeton ne soit renvoyé à votre application client.beforeSignIn
: se déclenche après la vérification des informations d'identification d'un utilisateur, mais avant que l'authentification Firebase ne renvoie un jeton d'identification à votre application client. 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 égalementbeforeSignIn
, en plus debeforeCreate
.
Gardez les points suivants à l’esprit lorsque vous utilisez les fonctions de blocage :
Votre fonction doit répondre dans les 7 secondes. Après 7 secondes, l'authentification Firebase 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 client . L'authentification Firebase 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 redéclenche toutes les fonctions enregistrées
beforeSignIn
.L'authentification anonyme et personnalisée ne déclenche pas de fonctions de blocage.
Déployer une fonction de blocage
Pour insérer votre code personnalisé dans les flux d'authentification des utilisateurs, déployez des fonctions de blocage. Une fois vos fonctions de blocage déployées, votre code personnalisé doit être exécuté avec succès pour que l'authentification et la création d'utilisateurs réussissent.
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é:
Écrivez des fonctions Cloud qui gèrent l'événement
beforeCreate
, l'événementbeforeSignIn
, ou les deux.Par exemple, pour commencer, vous pouvez ajouter les fonctions sans opération 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.
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.
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 convient d'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 | Description | Exemple |
---|---|---|
locale | 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 des 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'inscrit 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, tel que 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 le locataire d'authentification Firebase. | projects/ project-id /tenants/ tenant-id |
timestamp | Heure à laquelle l'événement a été déclenché, formatée sous forme de 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:
Noeud.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 requête 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, invalide ou expiré. |
permission-denied | 403 | Le client ne dispose pas d'une 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 limite 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 non disponible. |
deadline-exceeded | 504 | Délai de demande dépassé. |
Vous pouvez également spécifier un message d'erreur personnalisé :
Noeud.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 :
Noeud.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
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énements 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 :
Noeud.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
remplace la valeur définie dans beforeCreate
. Pour sessionClaims
uniquement, elles sont propagées aux revendications de jeton de la session en cours, mais ne sont ni conservées ni stockées 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'elles sont fusionnées, si une clé sessionClaims
correspond à une clé dans customClaims
, les customClaims
correspondantes seront écrasées dans les revendications de jeton par la clé sessionClaims
. Cependant, la clé customClaims
surchargée sera toujours conservée dans la base de données pour les demandes futures.
Informations d'identification et données OAuth prises 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 | Réclamations de connexion |
---|---|---|---|---|---|---|
Oui | Oui | Oui | Non | Oui | Non | |
Non | Oui | Oui | Non | Non | Non | |
Non | Oui | Non | Oui | Non | Non | |
GitHub | Non | Oui | Non | Non | Non | Non |
Microsoft | Oui | Oui | Oui | Non | Oui | Non |
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é ainsi que leurs informations d'identification et données prises en charge.
Fournisseurs OIDC génériques
Lorsqu'un utilisateur se connecte auprès d'un fournisseur OIDC générique, les informations d'identification suivantes seront transmises :
- Jeton d'identification : 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);
Lorsqu'un utilisateur se connecte avec Google, les informations d'identification suivantes seront 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 portée 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 .
Lorsqu'un utilisateur se connecte avec Facebook, les informations d'identification suivantes seront transmises :
- Jeton d'accès : un jeton d'accès est renvoyé et peut être échangé contre un autre jeton d'accès. Apprenez-en davantage sur les différents types de jetons d'accès pris en charge par Facebook et sur la façon dont vous pouvez les échanger contre des jetons à longue durée de vie .
GitHub
Lorsqu'un utilisateur se connecte avec GitHub, les informations d'identification suivantes seront transmises :
- Jeton d'accès : n'expire pas sauf révocation.
Microsoft
Lorsqu'un utilisateur se connecte avec Microsoft, les informations d'identification suivantes seront transmises :
- Jeton d'identification
- Jeton d'accès
- Jeton d'actualisation : transmis à 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 avec Yahoo, les informations d'identification suivantes seront transmises sans aucun paramètre ni étendue personnalisé :
- Jeton d'identification
- Jeton d'accès
- Jeton d'actualisation
Lorsqu'un utilisateur se connecte avec LinkedIn, les informations d'identification suivantes seront transmises :
- Jeton d'accès
Pomme
Lorsqu'un utilisateur se connecte avec Apple, les informations d'identification suivantes seront transmises sans aucun paramètre ni étendue personnalisé :
- Jeton d'identification
- Jeton d'accès
- Jeton d'actualisation
Scénarios courants
Les exemples suivants illustrent quelques 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'inscrire sur votre application :
Noeud.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 disposant d'adresses e-mail non vérifiées de s'inscrire sur votre application :
Noeud.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 par 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 :
Noeud.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 fournisseurs d'identité comme vérifiés
L'exemple suivant montre comment traiter les e-mails des utilisateurs provenant de certains fournisseurs d'identité comme vérifiés :
Noeud.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
return {
emailVerified: true,
};
}
});
Blocage de la connexion à partir de certaines adresses IP
L'exemple suivant montre comment bloquer la connexion à partir de certaines plages d'adresses IP :
Noeud.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (isSuspiciousIpAddress(context.ipAddress)) {
throw new functions.auth.HttpsError(
'permission-denied', 'Unauthorized access!');
}
});
Définition de revendications personnalisées et de session
L'exemple suivant montre comment définir des revendications personnalisées et de session :
Noeud.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 jetons 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.
Utilisez les revendications de session pour suivre l'adresse IP avec laquelle l'utilisateur se connecte :
Noeud.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { return { sessionClaims: { signInIpAddress: context.ipAddress, }, }; });
Lorsqu'un utilisateur tente d'accéder à des ressources nécessitant une authentification avec l'authentification Firebase, comparez l'adresse IP indiquée dans la requête avec l'adresse IP utilisée pour se connecter :
Noeud.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!' }) }); } }); });
Filtrage des photos des utilisateurs
L'exemple suivant montre comment nettoyer les photos de profil des utilisateurs :
Noeud.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 connecté avec Google et l'utiliser pour appeler les API de Google Calendar. Le jeton d'actualisation est stocké pour un accès hors ligne.
Noeud.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();
});
});
})
}
});
Remplacer le verdict de reCAPTCHA Enterprise pour le fonctionnement de l'utilisateur
L'exemple suivant montre comment remplacer un verdict reCAPTCHA Enterprise pour les flux d'utilisateurs pris en charge.
Reportez-vous à Activer reCAPTCHA Enterprise pour en savoir plus sur l'intégration de reCAPTCHA Enterprise avec l'authentification Firebase.
Les fonctions de blocage peuvent être utilisées pour autoriser ou bloquer les flux en fonction de facteurs personnalisés, remplaçant ainsi le résultat fourni par reCAPTCHA Enterprise.
Noeud.js
const {
auth,
} = require("firebase-functions/v1");
exports.checkrecaptchaV1 = auth.user().beforeSignIn((userRecord, context) => {
// Allow users with a specific email domain to sign in regardless of their recaptcha score.
if (userRecord.email && userRecord.email.indexOf('@acme.com') === -1) {
return {
recaptchaActionOverride: 'ALLOW',
};
}
// Allow users to sign in with recaptcha score greater than 0.5
if (context.additionalUserInfo.recaptchaScore > 0.5) {
return {
recaptchaActionOverride: 'ALLOW',
};
}
// Block all others.
return {
recaptchaActionOverride: 'BLOCK',
};
});