Únete de manera presencial y en línea a Firebase Summit el 18 de octubre de 2022. Descubre cómo Firebase puede ayudarte a acelerar el desarrollo de apps, a lanzar la tuya con confianza y a escalar con facilidad. Regístrate ahora

Extienda la autenticación de Firebase con el bloqueo de las funciones de la nube

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 activa beforeSignIn , además de beforeCreate .

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:

  1. Escriba funciones en la nube que controlen el evento beforeCreate , el evento beforeSignIn 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.

  2. 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

  1. Vaya a la página Configuración de autenticación de Firebase en la consola de Firebase.

  2. Seleccione la pestaña Funciones de bloqueo .

  3. 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) .

  4. 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 iniciar beforeSignIn 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
Google No No
Facebook No No No No
Gorjeo No No No No
GitHub No No No No No
microsoft No No
LinkedIn No No No No
yahoo No No
Manzana No No
SAML No No No No No
OIDC No

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

Google

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 .

Facebook

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

LinkedIn

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.

  1. 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,
        },
      };
    });
    
  2. 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();
          });
      });
    })
  }
});