Extiende Firebase Authentication con funciones de bloqueo


Las funciones de bloqueo te permiten ejecutar un código personalizado que modifica el resultado de un usuario que se registra o accede a tu app. Por ejemplo, puedes evitar que se autentique un usuario si no cumple con ciertos criterios o actualizar la información de un usuario antes de mostrársela a tu app cliente.

Antes de comenzar

Para usar funciones de bloqueo, debes actualizar tu proyecto de Firebase a Firebase Authentication with Identity Platform. Si aún no lo hiciste, hazlo primero.

Información sobre las funciones de bloqueo

Puedes registrar funciones de bloqueo para estos eventos:

  • Antes de crear el usuario: Se activa antes de que se guarde un usuario nuevo en la base de datos de Firebase Authentication y antes de que se muestre un token a la app cliente.

  • Antes de que acceda el usuario: Se activa después de que se verifican las credenciales de un usuario, pero antes de que Firebase Authentication muestre un token de ID a tu app cliente. Si tu app usa la autenticación de varios factores, la función se activa después de que el usuario verifica el segundo factor. Ten en cuenta que crear un usuario nuevo también activa ambos eventos.

  • Antes de enviar un correo electrónico (solo para Node.js): Se activa antes de que se envíe un correo electrónico (por ejemplo,
    un correo electrónico de acceso o de restablecimiento de contraseña) a un usuario.

  • Antes de enviar un mensaje SMS (solo para Node.js): Se activa antes de que se envíe un mensaje SMS a un usuario, en casos como la autenticación de varios factores.

Ten en cuenta lo siguiente cuando uses funciones de bloqueo:

  • La función tiene 7 segundos para responder. Después de ese plazo, Firebase Authentication muestra un error y la operación del cliente falla.

  • Los códigos de respuesta HTTP que no sean 200 se pasan a las apps cliente. Asegúrate de que el código de cliente administre cualquier error que pueda mostrar tu función.

  • Las funciones se aplican a todos los usuarios de tu proyecto, incluidos los que se encuentren en un grupo de usuarios. Firebase Authentication proporciona información sobre los usuarios a tu función, incluidos los grupos de usuarios a los que pertenecen, de modo que puedas responder en consecuencia.

  • La vinculación de otro proveedor de identidad con una cuenta vuelve a activar cualquier función beforeUserSignedIn registrada.

  • La autenticación anónima y personalizada no activa funciones de bloqueo.

Implementa una función de bloqueo

Para insertar tu código personalizado en los flujos de autenticación de usuarios, implementa funciones de bloqueo. Una vez que lo hagas, el código personalizado debe completarse correctamente para que se lleven a cabo la autenticación y la creación del usuario.

Se implementa una función de bloqueo de la misma manera en que se implementa cualquier función (consulta la página de Introducción de Cloud Functions). En resumen:

  1. Escribe una función que controle el evento objetivo.

    Por ejemplo, para comenzar, puedes agregar una función no-op como la siguiente a tu fuente:

    import {
      beforeUserCreated,
    } from "firebase-functions/v2/identity";
    
    export const beforecreated = beforeUserCreated((event) => {
      // TODO
      return;
    });
    
    @identity_fn.before_user_created()
    def created_noop(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
        return
    

    En el ejemplo anterior, se omite la implementación de la lógica de autenticación personalizada. Consulta las siguientes secciones si necesitas información para implementar las funciones de bloqueo y Situaciones comunes para ver ejemplos específicos.

  2. Implementa tus funciones con Firebase CLI:

    firebase deploy --only functions
    

    Debes volver a implementar las funciones cada vez que las actualices.

Obtén información sobre el usuario y el contexto

Los eventos de bloqueo proporcionan un objeto AuthBlockingEvent que contiene información sobre el acceso del usuario. Usa estos valores en el código para determinar si permites que una operación continúe.

El objeto contiene las siguientes propiedades:

Nombre Descripción Ejemplo
locale Es la configuración regional de la aplicación. Para establecerla, puedes usar el SDK de cliente o pasar el encabezado de configuración regional en la API de REST. fr o sv-SE
ipAddress Es la dirección IP del dispositivo desde el que se registra o accede el usuario final. 114.14.200.1
userAgent Es el usuario-agente que activa la función de bloqueo. Mozilla/5.0 (X11; Linux x86_64)
eventId Es el identificador único del evento. rWsyPtolplG2TBFoOkkgyg
eventType Es el tipo de evento. Proporciona información sobre el nombre del evento, como beforeSignIn o beforeCreate, y el método de acceso asociado, como Google o correo electrónico y contraseña. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Siempre USER. USER
resource El proyecto o usuario de Firebase Authentication. projects/project-id/tenants/tenant-id
timestamp Es el momento en que se activó el evento, en formato de string RFC 3339. Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Un objeto que contiene información sobre el usuario. AdditionalUserInfo
credential Es un objeto que contiene información sobre la credencial del usuario. AuthCredential

Impide el registro o el acceso

Para bloquear un intento de acceso o registro, arroja un HttpsError en tu función. Por ejemplo:

import { HttpsError } from "firebase-functions/v2/identity";

throw new HttpsError('invalid-argument');
raise https_fn.HttpsError(
    code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT)

También puedes especificar un mensaje de error personalizado:

throw new HttpsError('permission-denied', 'Unauthorized request origin!');
raise https_fn.HttpsError(
    code=https_fn.FunctionsErrorCode.PERMISSION_DENIED,
    message="Unauthorized request origin!"
)

En el siguiente ejemplo, se muestra cómo impedir que se registren en tu app los usuarios que no son miembros de un dominio específico:

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  // (If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from event.resource, e.g. 'projects/project-id/tenant/tenant-id-1')

  // Only users of a specific domain can sign up.
  if (!user?.email?.includes('@acme.com')) {
    throw new HttpsError('invalid-argument', "Unauthorized email");
  }
});
# Block account creation with any non-acme email address.
@identity_fn.before_user_created()
def validatenewuser(
        event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
    # User data passed in from the CloudEvent.
    user = event.data

    # Only users of a specific domain can sign up.
    if user.email is None or "@acme.com" not in user.email:
        # Return None so that Firebase Auth rejects the account creation.
        raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
                                  message="Unauthorized email")

Independientemente de si usas un mensaje predeterminado o personalizado, Cloud Functions une el error y lo muestra al cliente como un error interno. Por ejemplo:

throw new HttpsError('invalid-argument', "Unauthorized email");
# Only users of a specific domain can sign up.
if user.email is None or "@acme.com" not in user.email:
    # Return None so that Firebase Auth rejects the account creation.
    raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
                              message="Unauthorized email")

Tu app debe detectar el error y procesarlo según corresponda. Por ejemplo:

import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth';

// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
const auth = getAuth();
try {
  const result = await createUserWithEmailAndPassword(auth)
  const idTokenResult = await result.user.getIdTokenResult();
  console.log(idTokenResult.claim.admin);
} catch(error) {
  if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
      // Display error.
    } else {
      // Registration succeeds.
    }
}

Modifica a un usuario

En vez de bloquear un intento de acceso o registro, puedes permitir que la operación continúe, pero modificar el objeto User que se guarda en la base de datos de Firebase Authentication y se muestra al cliente.

Si quieres modificar a un usuario, muestra un objeto de tu controlador de eventos que contenga los campos que deseas modificar. Puedes modificar los campos siguientes:

  • displayName
  • disabled
  • emailVerified
  • photoUrl
  • customClaims
  • sessionClaims (solo beforeUserSignedIn)

A 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 se conservan entre las sesiones del usuario.

En el siguiente ejemplo, se muestra cómo configurar un nombre visible predeterminado:

export const beforecreated = beforeUserCreated((event) => {
  return {
    // If no display name is provided, set it to "Guest".
    displayName: event.data.displayName || 'Guest'
  };
});
@identity_fn.before_user_created()
def setdefaultname(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
    return identity_fn.BeforeCreateResponse(
        # If no display name is provided, set it to "Guest".
        display_name=event.data.display_name if event.data.display_name is not None else "Guest")

Si registras un controlador de eventos para beforeUserCreated y beforeUserSignedIn, ten en cuenta que beforeUserSignedIn se ejecuta después de beforeUserCreated. Los campos de usuario actualizados en beforeUserCreated se pueden ver en beforeUserSignedIn. Si estableces un campo distinto de sessionClaims en ambos controladores de eventos, el valor establecido en beforeUserSignedIn reemplaza al valor establecido en beforeUserCreated. Solo para sessionClaims, se propagan a las reclamaciones de token de la sesión actual, pero no se conservan ni se almacenan en la base de datos.

Por ejemplo, si se configuran sessionClaims, beforeUserSignedIn las mostrará con cualquier reclamación beforeUserCreated y se combinarán. Cuando se combinan, si una clave de sessionClaims coincide con otra de customClaims, el elemento customClaims coincidente se reemplazará en los reclamos del token por la clave de sessionClaims. Sin embargo, la clave de customClaims reemplazada se mantendrá en la base de datos para solicitudes futuras.

Datos y credenciales de OAuth admitidos

Puedes pasar credenciales y datos de OAuth a las funciones de bloqueo desde varios proveedores de identidad. En la siguiente tabla, se muestran las credenciales y los datos compatibles con cada proveedor de identidad:

Proveedor de identidad Token de ID Token de acceso Hora de vencimiento Secret del token Token de actualización Reclamaciones de acceso
Google No No
Facebook No No No No
Twitter No No No No
GitHub No No No No No
Microsoft No No
LinkedIn No No No No
Yahoo No No
Apple No No
SAML No No No No No
OIDC No

Tokens de OAuth

Para usar un token de ID, un token de acceso o un token de actualización en una función de bloqueo, primero debes seleccionar la casilla de verificación de la página Funciones de bloqueo de Firebase console.

Ningún proveedor de identidad mostrará tokens de actualización cuando se acceda directamente con una credencial de OAuth, como un token de ID o de acceso. En este caso, la misma credencial de OAuth del cliente se pasará a la función de bloqueo.

En las siguientes secciones, se describe cada tipo de proveedor de identidad, así como sus credenciales y datos admitidos.

Proveedores genéricos de OIDC

Cuando un usuario accede con un proveedor de OIDC genérico, se pasan las siguientes credenciales:

  • Token de ID: Se proporciona si está seleccionado el flujo id_token.
  • Token de acceso: Se proporciona si está seleccionado el flujo de código. Ten en cuenta que, por el momento, el flujo de código solo es compatible con la API de REST.
  • Token de actualización: Se proporciona si está seleccionado el permiso offline_access.

Ejemplo:

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

Google

Cuando un usuario accede con Google, se pasan las siguientes credenciales:

  • Token de ID
  • Token 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 otorgó su consentimiento antes y no se solicitó un permiso nuevo

Ejemplo:

import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/auth';

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

Más información sobre los tokens de actualización de Google.

Facebook

Cuando un usuario accede con Facebook, se pasa la siguiente credencial:

  • Token de acceso: Se muestra un token de acceso que se puede intercambiar por otro. Obtén más información sobre los diferentes tipos de tokens de acceso compatibles con Facebook y cómo puedes intercambiarlos por tokens de larga duración.

GitHub

Cuando un usuario accede con GitHub, se pasa la siguiente credencial:

  • Token de acceso: No vence, a menos que lo revoques.

Microsoft

Cuando un usuario accede con Microsoft, se pasan las siguientes credenciales:

  • Token de ID
  • Token de acceso
  • Token de actualización: Se pasa a la función de bloqueo si está seleccionado el permiso offline_access.

Ejemplo:

import { getAuth, signInWithPopup, OAuthProvider } from 'firebase/auth';

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

Yahoo

Cuando un usuario accede con Yahoo, se pasan las siguientes credenciales sin ningún parámetro ni permiso personalizado:

  • Token de ID
  • Token de acceso
  • Token de actualización

LinkedIn

Cuando un usuario accede con LinkedIn, se pasa la siguiente credencial:

  • Token de acceso

Apple

Cuando un usuario accede con Apple, se pasan las siguientes credenciales sin ningún parámetro o permiso personalizado:

  • Token de ID
  • Token de acceso
  • Token de actualización

Situaciones comunes

En los siguientes ejemplos, se muestran algunos casos de uso comunes de las funciones de bloqueo:

Solo permite registros de un dominio específico

En el siguiente ejemplo, se muestra cómo impedir que se registren en tu app los usuarios que no forman parte del dominio example.com:

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (!user?.email?.includes('@example.com')) {
    throw new HttpsError(
      'invalid-argument', 'Unauthorized email');
  }
});
 @identity_fn.before_user_created()
   def validatenewuser(
       event: identity_fn.AuthBlockingEvent,
   ) -> identity_fn.BeforeCreateResponse | None:
       # User data passed in from the CloudEvent.
       user = event.data

       # Only users of a specific domain can sign up.
       if user.email is None or "@example.com" not in user.email:
           # Return None so that Firebase Auth rejects the account creation.
           raise https_fn.HttpsError(
               code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
               message="Unauthorized email",
           )

Impide que se registren los usuarios con correos electrónicos no verificados

En el siguiente ejemplo, se muestra cómo impedir que se registren en tu app los usuarios con correos electrónicos no verificados:

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (user.email && !user.emailVerified) {
    throw new HttpsError(
      'invalid-argument', 'Unverified email');
  }
});
@identity_fn.before_user_created()
def requireverified(
        event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
    if event.data.email is not None and not event.data.email_verified:
        raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
                                  message="You must register using a trusted provider.")

Trata ciertos correos electrónicos de los proveedores de identidad como verificados

En el siguiente ejemplo, se muestra cómo tratar los correos electrónicos de los usuarios de ciertos proveedores de identidad como verificados:

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (user.email && !user.emailVerified && event.eventType.includes(':facebook.com')) {
    return {
      emailVerified: true,
    };
  }
});
@identity_fn.before_user_created()
def markverified(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
    if event.data.email is not None and "@facebook.com" in event.data.email:
        return identity_fn.BeforeSignInResponse(email_verified=True)

Impide el acceso desde ciertas direcciones IP

En el siguiente ejemplo, se muestra cómo bloquear el acceso desde ciertos rangos de direcciones IP:

export const beforesignedin = beforeUserSignedIn((event) => {
  if (isSuspiciousIpAddress(event.ipAddress)) {
    throw new HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});
@identity_fn.before_user_signed_in()
def ipban(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None:
    if is_suspicious(event.ip_address):
        raise https_fn.HttpsError(code=https_fn.FunctionsErrorCode.PERMISSION_DENIED,
                                  message="IP banned.")

Configura reclamaciones personalizadas y de sesión

En el siguiente ejemplo, se muestra cómo configurar reclamaciones personalizadas y de sesión:

export const beforecreated = beforeUserCreated((event) => {
    if (event.credential &&
        event.credential.claims &&
        event.credential.providerId === "saml.my-provider-id") {
        return {
            // Employee ID does not change so save in persistent claims (stored in
            // Auth DB).
            customClaims: {
                eid: event.credential.claims.employeeid,
            },
        };
    }
});

export const beforesignin = beforeUserSignedIn((event) => {
    if (event.credential &&
        event.credential.claims &&
        event.credential.providerId === "saml.my-provider-id") {
        return {
            // Copy role and groups to token claims. These will not be persisted.
            sessionClaims: {
                role: event.credential.claims.role,
                groups: event.credential.claims.groups,
            },
        };
    }
});
@identity_fn.before_user_created()
def setemployeeid(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
    if (event.credential is not None and event.credential.claims is not None and
            event.credential.provider_id == "saml.my-provider-id"):
        return identity_fn.BeforeCreateResponse(
            custom_claims={"eid": event.credential.claims["employeeid"]})


@identity_fn.before_user_signed_in()
def copyclaimstosession(
        event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None:
    if (event.credential is not None and event.credential.claims is not None and
            event.credential.provider_id == "saml.my-provider-id"):
        return identity_fn.BeforeSignInResponse(session_claims={
            "role": event.credential.claims["role"],
            "groups": event.credential.claims["groups"]
        })

Haz un seguimiento de las direcciones IP para supervisar actividades sospechosas

Puedes evitar el robo de tokens realizando un seguimiento de la dirección IP desde la que accede un usuario y comparándola con la dirección IP de las solicitudes posteriores. Si la solicitud parece sospechosa (por ejemplo, las IP provienen de distintas regiones geográficas), puedes pedirle al usuario que vuelva a acceder.

  1. Usa reclamaciones de sesión para hacer un seguimiento de la dirección IP con la que accede el usuario:

    export const beforesignedin = beforeUserSignedIn((event) => {
      return {
        sessionClaims: {
          signInIpAddress: event.ipAddress,
        },
      };
    });
    
    @identity_fn.before_user_signed_in()
    def logip(event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeSignInResponse | None:
        return identity_fn.BeforeSignInResponse(session_claims={"signInIpAddress": event.ip_address})
    
  2. Cuando un usuario intenta acceder a los recursos que requieren autenticación con Firebase Authentication, compara la dirección IP de la solicitud con la IP que se usó para acceder:

    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!' })
          });
        }
      });
    });
    
    from firebase_admin import auth, initialize_app
    import flask
    
    initialize_app()
    flask_app = flask.Flask(__name__)
    
    @flask_app.post()
    def get_restricted_data(req: flask.Request):
        # Get the ID token passed.
        id_token = req.json().get("idToken")
    
        # Verify the ID token, check if revoked, and decode its payload.
        try:
            claims = auth.verify_id_token(id_token, check_revoked=True)
        except:
            return flask.Response(status=500)
    
        # Get request IP address.
        request_ip = req.remote_addr
    
        # Get sign-in IP address.
        signin_ip = 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 is_suspicious_change(signin_ip, request_ip):
            # Suspicious IP address change. Require re-authentication.
            # You can also revoke all user sessions by calling:
            #   auth.revoke_refresh_tokens(claims["sub"])
            return flask.Response(status=401,
                                  response="Unauthorized access. Sign in again!")
        else:
            # Access is valid. Try to return data.
            return data_from_claims(claims)
    

Analiza fotos del usuario

En el siguiente ejemplo, se muestra cómo limpiar las fotos de perfil de los usuarios:

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  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,
          };
        }
      });
});
@identity_fn.before_user_created()
def sanitizeprofilephoto(
        event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
    if event.data.photo_url is not None:
        score = analyze_photo_with_ml(event.data.photo_url)
        if score > THRESHOLD:
            return identity_fn.BeforeCreateResponse(photo_url=PLACEHOLDER_URL)

Si necesitas más información para detectar y limpiar imágenes, consulta la documentación de Cloud Vision.

Accede a las credenciales de OAuth del proveedor de identidad de un usuario

En el siguiente ejemplo, se muestra cómo obtener un token de actualización para un usuario que accedió con Google y usarlo a fin de llamar a las APIs de Google Calendar. El token de actualización se almacena para el acceso sin conexión.

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

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (event.credential &&
      event.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,
        event.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:
          // event.additionalUserInfo.profile.granted_scopes (space joined list of scopes).

          // Set access token/refresh token.
          oAuth2Client.setCredentials({
            access_token: event.credential.accessToken,
            refresh_token: event.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();
          });
      });
    })
  }
});
@identity_fn.before_user_created()
def savegoogletoken(
        event: identity_fn.AuthBlockingEvent) -> identity_fn.BeforeCreateResponse | None:
    """During sign-up, save the Google OAuth2 access token and queue up a task
    to schedule an onboarding session on the user's Google Calendar.

    You will only get an access token if you enabled it in your project's blocking
    functions settings in the Firebase console:

    https://console.firebase.google.com/project/_/authentication/settings
    """
    if event.credential is not None and event.credential.provider_id == "google.com":
        print(f"Signed in with {event.credential.provider_id}. Saving access token.")

        firestore_client: google.cloud.firestore.Client = firestore.client()
        doc_ref = firestore_client.collection("user_info").document(event.data.uid)
        doc_ref.set({"calendar_access_token": event.credential.access_token}, merge=True)

        tasks_client = google.cloud.tasks_v2.CloudTasksClient()
        task_queue = tasks_client.queue_path(params.PROJECT_ID.value,
                                             options.SupportedRegion.US_CENTRAL1,
                                             "scheduleonboarding")
        target_uri = get_function_url("scheduleonboarding")
        calendar_task = google.cloud.tasks_v2.Task(http_request={
            "http_method": google.cloud.tasks_v2.HttpMethod.POST,
            "url": target_uri,
            "headers": {
                "Content-type": "application/json"
            },
            "body": json.dumps({
                "data": {
                    "uid": event.data.uid
                }
            }).encode()
        },
                                                   schedule_time=datetime.now() +
                                                   timedelta(minutes=1))
        tasks_client.create_task(parent=task_queue, task=calendar_task)

Anula el veredicto de reCAPTCHA Enterprise para la operación del usuario

En el siguiente ejemplo, se muestra cómo anular un veredicto de reCAPTCHA Enterprise para los flujos de usuarios admitidos.

Consulta Habilita reCAPTCHA Enterprise para obtener más información sobre cómo integrar reCAPTCHA Enterprise con Firebase Authentication.

Las funciones de bloqueo se pueden usar para permitir o bloquear flujos en función de factores personalizados, lo que anula el resultado que proporciona reCAPTCHA Enterprise.

const { beforeSmsSent } = require("firebase-functions/v2/identity");
exports.beforesmssentv2 = beforeSmsSent((event) => {
 if (
   event.smsType === "SIGN_IN_OR_SIGN_UP" &&
   event.additionalUserInfo.phoneNumber.includes('+91')
 ) {
   return {
     recaptchaActionOverride: "ALLOW",
   };
 }

 // Allow users to sign in with recaptcha score greater than 0.5
 if (event.additionalUserInfo.recaptchaScore > 0.5) {
   return {
     recaptchaActionOverride: 'ALLOW',
   };
 }

 // Block all others.
 return  {
   recaptchaActionOverride: 'BLOCK',
 }
});