Si actualizó a Firebase Authentication con Identity Platform , puede extender Firebase Authentication mediante el bloqueo de Cloud Functions .
Las funciones de bloqueo le permiten ejecutar un código personalizado que modifica el resultado de un usuario que se registra o inicia sesión en su aplicación. Por ejemplo, puede evitar que un usuario se autentique si no cumple con ciertos criterios o actualizar la información de un usuario antes de devolverla a su aplicación cliente.
Antes de que empieces
Para usar las funciones de bloqueo, debe actualizar su proyecto de Firebase a Firebase Authentication con Identity Platform. Si aún no ha actualizado, hágalo primero.
Comprender las funciones de bloqueo
Puede registrar funciones de bloqueo para dos eventos:
beforeCreate
: se activa antes de que se guarde un nuevo usuario en la base de datos de autenticación de Firebase y antes de que se devuelva un token a su aplicación cliente.beforeSignIn
: se activa después de que se verifiquen las credenciales de un usuario, pero antes de que Firebase Authentication devuelva un token de ID a su aplicación cliente. Si su aplicación usa autenticación multifactor, la función se activa después de que el usuario verifique su segundo factor. Tenga en cuenta que la creación de un nuevo usuario también activabeforeSignIn
, además debeforeCreate
.
Tenga en cuenta lo siguiente cuando utilice funciones de bloqueo:
Su función debe responder en 7 segundos. Después de 7 segundos, Firebase Authentication devuelve un error y la operación del cliente falla.
Los códigos de respuesta HTTP que no sean
200
se pasan a sus aplicaciones cliente. Asegúrese de que su código de cliente maneje cualquier error que su función pueda devolver.Las funciones se aplican a todos los usuarios de su proyecto, incluidos los contenidos en un arrendatario . Firebase Authentication proporciona información sobre los usuarios de su función, incluidos los inquilinos a los que pertenecen, para que pueda responder en consecuencia.
Vincular otro proveedor de identidad a una cuenta vuelve a
beforeSignIn
cualquier función antes de iniciar sesión registrada.La autenticación anónima y personalizada no activa funciones de bloqueo.
Implementar y registrar una función de bloqueo
Para insertar su código personalizado en los flujos de autenticación de usuarios, implemente y registre funciones de bloqueo. Una vez que sus funciones de bloqueo se implementan y registran, su código personalizado debe completarse correctamente para que la autenticación y la creación de usuarios se realicen correctamente.
Implementar una función de bloqueo
Implementa una función de bloqueo de la misma manera que implementa cualquier función. (Consulte la página de inicio de Cloud Functions para obtener más información). En resumen:
Escriba funciones en la nube que controlen el evento
beforeCreate
, el eventobeforeSignIn
o ambos.Por ejemplo, para comenzar, puede agregar las siguientes funciones no operativas 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 });
Los ejemplos anteriores han omitido la implementación de la lógica de autenticación personalizada. Consulte las siguientes secciones para aprender cómo implementar sus funciones de bloqueo y escenarios comunes para ejemplos específicos.
Implemente sus funciones con Firebase CLI:
firebase deploy --only functions
Debe volver a implementar sus funciones cada vez que las actualice.
Registrar una función de bloqueo
Vaya a la página Configuración de autenticación de Firebase en la consola de Firebase.
Seleccione la pestaña Funciones de bloqueo .
Registre su función de bloqueo seleccionándola del menú desplegable en Antes de la creación de la cuenta (beforeCreate) o Antes de iniciar sesión (beforeSignIn) .
Guarde sus cambios.
Obtener información del usuario y del contexto
Los eventos beforeSignIn
y beforeCreate
proporcionan objetos User
y EventContext
que contienen información sobre el inicio de sesión del usuario. Use estos valores en su código para determinar si permite que una operación continúe.
Para obtener una lista de las propiedades disponibles en el objeto User
, consulte la referencia de la API UserRecord
.
El objeto EventContext
contiene las siguientes propiedades:
Nombre | Descripción | Ejemplo |
---|---|---|
locale | La configuración regional de la aplicación. Puede establecer la configuración regional mediante el SDK del cliente o pasando el encabezado de la configuración regional en la API de REST. | fr o sv-SE |
ipAddress | La dirección IP del dispositivo desde el que el usuario final se está registrando o iniciando sesión. | 114.14.200.1 |
userAgent | El agente de usuario que activa la función de bloqueo. | Mozilla/5.0 (X11; Linux x86_64) |
eventId | El identificador único del evento. | rWsyPtolplG2TBFoOkkgyg |
eventType | El tipo de evento. Esto proporciona información sobre el nombre del evento, como beforeSignIn o beforeCreate , y el método de inicio de sesión asociado utilizado, como Google o correo electrónico/contraseña. | providers/cloud.auth/eventTypes/user.beforeSignIn:password |
authType | Siempre USER . | USER |
resource | El proyecto o inquilino de Firebase Authentication. | projects/ project-id /tenants/ tenant-id |
timestamp | La hora en que se activó el evento, con formato de cadena RFC 3339 . | Tue, 23 Jul 2019 21:10:57 GMT |
additionalUserInfo | Un objeto que contiene información sobre el usuario. | AdditionalUserInfo |
credential | Un objeto que contiene información sobre la credencial del usuario. | AuthCredential |
Bloqueo de registro o inicio de sesión
Para bloquear un intento de registro o inicio de sesión, inicie un HttpsError
en su función. Por ejemplo:
Nodo.js
throw new functions.auth.HttpsError('permission-denied');
La siguiente tabla enumera los errores que puede generar, junto con su mensaje de error predeterminado:
Nombre | Código | Mensaje |
---|---|---|
invalid-argument | 400 | El cliente especificó un argumento no válido. |
failed-precondition | 400 | La solicitud no se puede ejecutar en el estado actual del sistema. |
out-of-range | 400 | El cliente especificó un rango no válido. |
unauthenticated | 401 | Token de OAuth faltante, no válido o caducado. |
permission-denied | 403 | El cliente no tiene suficientes permisos. |
not-found | 404 | No se encuentra el recurso especificado. |
aborted | 409 | Conflicto de simultaneidad, como un conflicto de lectura, modificación y escritura. |
already-exists | 409 | El recurso que un cliente intentó crear ya existe. |
resource-exhausted | 429 | Fuera de la cuota de recursos o llegando al límite de velocidad. |
cancelled | 499 | Solicitud cancelada por el cliente. |
data-loss | 500 | Pérdida irrecuperable de datos o corrupción de datos. |
unknown | 500 | Error de servidor desconocido. |
internal | 500 | Error de servidor interno. |
not-implemented | 501 | Método API no implementado por el servidor. |
unavailable | 503 | Servicio no disponible. |
deadline-exceeded | 504 | Plazo de solicitud excedido. |
También puede especificar un mensaje de error personalizado:
Nodo.js
throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');
El siguiente ejemplo muestra cómo bloquear a los usuarios que no están dentro de un dominio específico para que no se registren en su aplicación:
Nodo.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}"`);
}
});
Independientemente de si usa un mensaje predeterminado o personalizado, Cloud Functions envuelve el error y lo devuelve al cliente como un error interno. Por ejemplo:
throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);
Su aplicación debe detectar el error y manejarlo en consecuencia. Por ejemplo:
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.
}
});
Modificando un usuario
En lugar de bloquear un intento de registro o inicio de sesión, puede permitir que la operación continúe, pero modifique el objeto User
que se guarda en la base de datos de Firebase Authentication y se devuelve al cliente.
Para modificar un usuario, devuelva un objeto de su controlador de eventos que contenga los campos para modificar. Puede modificar los siguientes campos:
-
displayName
-
disabled
-
emailVerified
-
photoURL
-
customClaims
-
sessionClaims
(antes de iniciarbeforeSignIn
solamente)
Con la excepción de sessionClaims
, todos los campos modificados se guardan en la base de datos de Firebase Authentication, lo que significa que se incluyen en el token de respuesta y persisten entre sesiones de usuario.
El siguiente ejemplo muestra cómo establecer un nombre para mostrar predeterminado:
Nodo.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 registra un controlador de eventos para beforeCreate
y beforeSignIn
, tenga en cuenta que beforeSignIn
ejecuta después de beforeCreate
. Los campos de usuario actualizados en beforeCreate
son visibles en beforeSignIn
. Si establece un campo que no sea sessionClaims
en ambos controladores de eventos, el valor establecido en beforeSignIn
sobrescribe el valor establecido en beforeCreate
. Solo para sessionClaims
, se propagan a las notificaciones de token de la sesión actual, pero no se conservan ni se almacenan en la base de datos.
Por ejemplo, si se establece cualquier sessionClaims
, beforeSignIn
los devolverá con cualquier beforeCreate
y se fusionarán. Cuando se fusionan, si una clave sessionClaims
coincide con una clave en customClaims
, la clave customClaims
sobrescribirá las sessionClaims
coincidentes en las notificaciones del token. Sin embargo, la clave overwitten customClaims
aún permanecerá en la base de datos para futuras solicitudes.
Credenciales y datos de OAuth admitidos
Puede pasar credenciales y datos de OAuth a funciones de bloqueo de varios proveedores de identidad. La siguiente tabla muestra qué credenciales y datos son compatibles con cada proveedor de identidad:
Proveedor de identidad | ficha de identificación | Ficha de acceso | Tiempo de expiración | Secreto simbólico | Ficha de actualización | Reclamaciones de inicio de sesión |
---|---|---|---|---|---|---|
Sí | Sí | Sí | No | Sí | No | |
No | Sí | Sí | No | No | No | |
Gorjeo | No | Sí | No | Sí | No | No |
GitHub | No | Sí | No | No | No | No |
microsoft | Sí | Sí | Sí | No | Sí | No |
No | Sí | Sí | No | No | No | |
yahoo | Sí | Sí | Sí | No | Sí | No |
Manzana | Sí | Sí | Sí | No | Sí | No |
SAML | No | No | No | No | No | Sí |
OIDC | Sí | Sí | Sí | No | Sí | Sí |
Fichas de actualización
Para usar un token de actualización en una función de bloqueo, primero debe seleccionar la casilla de verificación en la página Funciones de bloqueo de Firebase console.
Ningún proveedor de identidad devolverá tokens de actualización al iniciar sesión directamente con una credencial de OAuth, como un token de ID o un token de acceso. En esta situación, la misma credencial de OAuth del lado del cliente se pasará a la función de bloqueo.
Las siguientes secciones describen cada tipo de proveedor de identidad y sus credenciales y datos admitidos.
Proveedores genéricos de OIDC
Cuando un usuario inicia sesión con un proveedor de OIDC genérico, se pasarán las siguientes credenciales:
- Token de ID : se proporciona si se selecciona el flujo
id_token
. - Token de acceso : se proporciona si se selecciona el flujo de código. Tenga en cuenta que el flujo de código solo se admite actualmente a través de la API REST.
- Token de actualización : se proporciona si se selecciona el ámbito
offline_access
.
Ejemplo:
const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
Cuando un usuario inicia sesión con Google, se pasarán las siguientes credenciales:
- token de identificación
- ficha de acceso
- Token de actualización : solo se proporciona si se solicitan los siguientes parámetros personalizados:
-
access_type=offline
-
prompt=consent
, si el usuario previamente dio su consentimiento y no se solicitó un nuevo alcance
-
Ejemplo:
const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
'access_type': 'offline',
'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);
Obtén más información sobre los tokens de actualización de Google .
Cuando un usuario inicia sesión con Facebook, se pasará la siguiente credencial:
- Token de acceso : se devuelve un token de acceso que se puede cambiar por otro token de acceso. Obtenga más información sobre los diferentes tipos de tokens de acceso admitidos por Facebook y cómo puede canjearlos por tokens de larga duración .
GitHub
Cuando un usuario inicia sesión con GitHub, se pasará la siguiente credencial:
- Token de acceso : no caduca a menos que se revoque.
microsoft
Cuando un usuario inicia sesión con Microsoft, se pasarán las siguientes credenciales:
- token de identificación
- ficha de acceso
- Token de actualización : se pasa a la función de bloqueo si se selecciona el alcance
offline_access
.
Ejemplo:
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
yahoo
Cuando un usuario inicia sesión con Yahoo, las siguientes credenciales se pasarán sin parámetros ni ámbitos personalizados:
- token de identificación
- token de acceso
- token de actualización
Cuando un usuario inicia sesión con LinkedIn, se pasará la siguiente credencial:
- token de acceso
Manzana
Cuando un usuario inicia sesión con Apple, las siguientes credenciales se pasarán sin parámetros ni ámbitos personalizados:
- token de identificación
- token de acceso
- token de actualización
Escenarios comunes
Los siguientes ejemplos demuestran algunos casos de uso comunes para funciones de bloqueo:
Solo permitir el registro desde un dominio específico
El siguiente ejemplo muestra cómo evitar que los usuarios que no forman parte del dominio example.com
se registren en su aplicación:
Nodo.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}"`);
}
});
Bloquear a los usuarios con correos electrónicos no verificados para que no se registren
El siguiente ejemplo muestra cómo evitar que los usuarios con correos electrónicos no verificados se registren en su aplicación:
Nodo.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}"`);
}
});
Requerir verificación de correo electrónico en el registro
El siguiente ejemplo muestra cómo solicitar a un usuario que verifique su correo electrónico después de registrarse:
Nodo.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.`);
}
});
Tratar ciertos correos electrónicos de proveedores de identidad como verificados
El siguiente ejemplo muestra cómo tratar los correos electrónicos de los usuarios de ciertos proveedores de identidad como verificados:
Nodo.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
return {
emailVerified: true,
};
}
});
Bloqueo de inicio de sesión desde ciertas direcciones IP
El siguiente ejemplo de cómo bloquear el inicio de sesión desde ciertos rangos de direcciones IP:
Nodo.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (isSuspiciousIpAddress(context.ipAddress)) {
throw new functions.auth.HttpsError(
'permission-denied', 'Unauthorized access!');
}
});
Configuración de reclamaciones personalizadas y de sesión
El siguiente ejemplo muestra cómo establecer reclamos personalizados y de sesión:
Nodo.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,
}
}
}
});
Seguimiento de direcciones IP para monitorear actividad sospechosa
Puede evitar el robo de tokens rastreando la dirección IP desde la que un usuario inicia sesión y comparándola con la dirección IP en solicitudes posteriores. Si la solicitud parece sospechosa, por ejemplo, las IP son de diferentes regiones geográficas, puede pedirle al usuario que inicie sesión nuevamente.
Use reclamos de sesión para rastrear la dirección IP con la que el usuario inicia sesión:
Nodo.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { return { sessionClaims: { signInIpAddress: context.ipAddress, }, }; });
Cuando un usuario intenta acceder a recursos que requieren autenticación con Firebase Authentication, compare la dirección IP en la solicitud con la IP utilizada para iniciar sesión:
Nodo.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!' }) }); } }); });
Proyección de fotos de usuarios
El siguiente ejemplo muestra cómo desinfectar las fotos de perfil de los usuarios:
Nodo.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,
};
}
});
});
Para obtener más información sobre cómo detectar y desinfectar imágenes, consulte la documentación de Cloud Vision .
Acceso a las credenciales de OAuth del proveedor de identidad de un usuario
El siguiente ejemplo muestra cómo obtener un token de actualización para un usuario que inició sesión con Google y usarlo para llamar a las API de Google Calendar. El token de actualización se almacena para el acceso sin conexión.
Nodo.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();
});
});
})
}
});