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 égalementbeforeSignIn
, en plus debeforeCreate
.
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é:
É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 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.
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
Accédez à la page Paramètres d'authentification Firebase dans la console Firebase.
Sélectionnez l'onglet Fonctions de blocage .
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) .
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 | 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 essayé 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 non disponible. |
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
écrasée 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 |
---|---|---|---|---|---|---|
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é 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);
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 .
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
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.
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, }, }; });
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();
});
});
})
}
});