Триггеры блокировки аутентификации


Функции блокировки позволяют выполнять собственный код, который изменяет результат регистрации или входа пользователя в ваше приложение. Например, вы можете запретить пользователю аутентификацию, если он не соответствует определенным критериям, или обновить информацию о пользователе, прежде чем вернуть ее в клиентское приложение.

Подготовка

Чтобы использовать функции блокировки, необходимо обновить проект Firebase до версии Firebase Authentication with Identity Platform. Если вы ещё не перешли на новую версию, сделайте это.

Функции блокировки

Вы можете зарегистрировать блокирующие функции для следующих событий:

  • До создания пользователя. Триггеры до того, как новый пользователь будет сохранен в базе данных Firebase Authentication и до того, как токен будет возвращен клиентскому приложению.

  • До входа пользователя в аккаунт. Функция запускается после проверки учетных данных пользователя, но до того, как Firebase Authentication вернет токен идентификатора клиентскому приложению. Если в приложении используется многофакторная аутентификация, функция запускается после того, как пользователь подтвердит второй фактор. Обратите внимание, что создание нового пользователя также активирует оба этих события.

  • Перед отправкой электронного письма (только для Node.js). Триггер срабатывает перед отправкой пользователю электронного письма, например для входа в аккаунт или сброса пароля.

  • Перед отправкой SMS-сообщения (только для Node.js). Триггер срабатывает перед отправкой SMS-сообщения пользователю, например для многофакторной аутентификации.

При использовании функций блокировки учитывайте следующее:

  • Функция должна ответить в течение семи секунд. Через семь секунд Firebase Authentication возвращает ошибку, и операция клиента завершается неудачно.

  • Коды ответов HTTP, отличные от 200, передаются клиентским приложениям. Убедитесь, что код клиента обрабатывает все ошибки, которые может вернуть функция.

  • Функции применяются ко всем пользователям в проекте, в том числе к тем, которые содержатся в клиенте. Firebase Authentication предоставляет вашей функции информацию о пользователях, в том числе о том, к каким клиентам они относятся, чтобы вы могли соответствующим образом реагировать.

  • При связывании с аккаунтом другого поставщика идентификационной информации повторно запускаются все зарегистрированные функции beforeUserSignedIn.

  • Анонимная и специальная аутентификация не активируют функции блокировки.

Как развернуть функцию блокировки

Чтобы вставить собственный код в процесс аутентификации пользователя, разверните блокирующие функции. После того как функции блокировки будут развернуты, ваш код должен успешно завершить работу, чтобы аутентификация и создание пользователя прошли успешно.

Блокирующая функция развертывается так же, как и любая другая. Подробную информацию можно найти на странице Cloud Functions Начало работы. В целом можно выделить следующее.

  1. Напишите функцию, которая обрабатывает целевое событие.

    Например, для начала можно добавить в источник функцию без операций, как показано ниже:

    Node.js

    import {
      beforeUserCreated,
    } from "firebase-functions/v2/identity";
    
    export const beforecreated = beforeUserCreated((event) => {
      // TODO
      return;
    });
    

    Python

    @identity_fn.before_user_created()
    def created_noop(
        event: identity_fn.AuthBlockingEvent,
    ) -> identity_fn.BeforeCreateResponse | None:
        return
    

    В приведенном выше примере не реализована собственная логика аутентификации. В следующих разделах рассказывается, как реализовать функции блокировки, а в разделе Распространенные сценарии приведены конкретные примеры.

  2. Разверните функции с помощью интерфейса командной строки Firebase:

    firebase deploy --only functions
    

    После каждого изменения функции необходимо повторно развертывать.

Как получить информацию о пользователе и контексте

Блокирующие события предоставляют объект AuthBlockingEvent, содержащий информацию о входе пользователя в систему. Используйте эти значения в коде, чтобы определить, разрешить ли выполнение операции.

Объект содержит следующие свойства:

Имя Описание Пример
locale Локаль приложения. Вы можете задать локаль с помощью клиентского SDK или передать заголовок локали в REST API. fr или sv-SE
ipAddress IP-адрес устройства, с которого конечный пользователь регистрируется или выполняет вход. 114.14.200.1
userAgent Агент пользователя, который активирует функцию блокировки. Mozilla/5.0 (X11; Linux x86_64)
eventId Уникальный идентификатор события. rWsyPtolplG2TBFoOkkgyg
eventType Тип события. Здесь можно найти название события, например beforeSignIn или beforeCreate, и связанный с ним способ входа, например с помощью аккаунта Google или адреса электронной почты и пароля. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Всегда USER. USER
resource Проект или клиент Firebase Authentication. projects/project-id/tenants/tenant-id
timestamp Время активации события в формате строки RFC 3339. Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Объект, содержащий информацию о пользователе. AdditionalUserInfo
credential Объект, содержащий информацию об учетных данных пользователя. AuthCredential

Блокировка регистрации или входа

Чтобы заблокировать попытку регистрации или входа, добавьте в функцию исключение HttpsError. Пример:

Node.js

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

throw new HttpsError('invalid-argument');

Python

raise https_fn.HttpsError(
    code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT)

Вы также можете указать специальное сообщение об ошибке:

Node.js

throw new HttpsError('permission-denied', 'Unauthorized request origin!');

Python

raise https_fn.HttpsError(
    code=https_fn.FunctionsErrorCode.PERMISSION_DENIED,
    message="Unauthorized request origin!"
)

В следующем примере показано, как запретить регистрацию в приложении пользователям, не относящимся к определенному домену:

Node.js

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

Python

# 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",
        )

Независимо от того, используете вы стандартное или специальное сообщение, Cloud Functions обернет ошибку и вернет ее клиенту как внутреннюю. Пример:

Node.js

throw new HttpsError('invalid-argument', "Unauthorized email");

Python

# 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",
    )

Приложение должно перехватывать ошибку и обрабатывать ее. Пример:

JavaScript

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

Как изменить пользователя

Вместо того чтобы блокировать попытку регистрации или входа, вы можете разрешить продолжить операцию, но изменить объект User, который сохраняется в базе данных Firebase Authentication и возвращается клиенту.

Чтобы изменить пользователя, верните из обработчика событий объект, содержащий поля, которые нужно изменить. Вы можете изменить следующие поля:

  • displayName
  • disabled
  • emailVerified
  • photoURL
  • customClaims
  • sessionClaims (только beforeUserSignedIn)

За исключением поля sessionClaims, все измененные поля сохраняются в базе данных Firebase Authentication, а значит, включаются в токен ответа и сохраняются между сеансами пользователя.

В следующем примере показано, как задать название по умолчанию:

Node.js

export const beforecreated = beforeUserCreated((event) => {
  return {
    // If no display name is provided, set it to "Guest".
    displayName: event.data.displayName || 'Guest'
  };
});

Python

@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"
    )

Если вы зарегистрируете обработчик событий для beforeUserCreated и beforeUserSignedIn, то beforeUserSignedIn будет выполняться после beforeUserCreated. Поля пользователей, обновленные в beforeUserCreated, видны в beforeUserSignedIn. Если в обоих обработчиках событий задано поле, отличное от sessionClaims, значение, заданное в beforeUserSignedIn, перезаписывает значение, заданное в beforeUserCreated. Только для sessionClaims они передаются в утверждения токена текущего сеанса, но не сохраняются в базе данных.

Например, если заданы какие-либо sessionClaims, функция beforeUserSignedIn вернет их вместе с любыми заявками beforeUserCreated, и они будут объединены. Если при объединении ключ sessionClaims совпадает с ключом в customClaims, то ключ sessionClaims перезапишет ключ customClaims в утверждениях токена. Однако перезаписанный ключ customClaims будет сохранен в базе данных для будущих запросов.

Поддерживаемые учетные данные OAuth и данные

Вы можете передавать учетные данные OAuth и данные в блокирующие функции от разных поставщиков идентификационной информации. В таблице ниже указано, какие учетные данные и данные поддерживаются для каждого поставщика идентификационной информации:

Поставщик идентификации Токен идентификации Токен доступа Время окончания срока действия Секрет токена Токен обновления Заявки на вход
Google Да Да Да Нет Да Нет
Facebook Нет Да Да Нет Нет Нет
Twitter Нет Да Нет Да Нет Нет
GitHub Нет Да Нет Нет Нет Нет
Microsoft Да Да Да Нет Да Нет
LinkedIn Нет Да Да Нет Нет Нет
Yahoo Да Да Да Нет Да Нет
Apple Да Да Да Нет Да Нет
SAML Нет Нет Нет Нет Нет Да
OIDC Да Да Да Нет Да Да

Токены OAuth

Чтобы использовать токен идентификации, токен доступа или токен обновления в блокирующей функции, сначала установите флажок на странице Блокирующие функции консоли Firebase (перейдите на вкладку Настройки в разделе Безопасность > Аутентификация).

Поставщики идентификационной информации не будут возвращать токены обновления при входе с учетными данными OAuth, например с токеном доступа или идентификатором. В такой ситуации в блокирующую функцию будут переданы те же учетные данные OAuth на стороне клиента.

В следующих разделах описаны типы поставщиков идентификационной информации, а также поддерживаемые ими учетные данные и данные.

Поставщики Generic OIDC

Когда пользователь входит в систему с помощью поставщика OIDC, передаются следующие учетные данные:

  • Токен идентификатора предоставляется, если выбран поток id_token.
  • Токен доступа предоставляется, если выбран поток кода. Обратите внимание, что в настоящее время поток кода поддерживается только через REST API.
  • Токен обновления предоставляется, если выбран область действия offline_access.

Пример:

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

Google

Когда пользователь входит в аккаунт с помощью Google, передаются следующие учетные данные:

  • Токен идентификатора
  • Токен доступа
  • Маркер обновления. Предоставляется, только если запрошены следующие специальные параметры:
    • access_type=offline
    • prompt=consent, если пользователь ранее дал согласие и не запрашивались новые области действия.

Пример:

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

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

Подробнее о токенах обновления Google…

Facebook

Когда пользователь входит в аккаунт через Facebook, передаются следующие учетные данные:

  • Токен доступа. Возвращается токен доступа, который можно обменять на другой токен доступа. Подробнее о разных типах токенов доступа, поддерживаемых Facebook, и о том, как обменять их на долгосрочные токены…

GitHub

Когда пользователь входит в аккаунт с помощью GitHub, передаются следующие учетные данные:

  • Токен доступа не имеет срока действия, если не отозван.

Microsoft

Когда пользователь входит в аккаунт с помощью Microsoft, передаются следующие учетные данные:

  • Токен идентификатора
  • Токен доступа
  • Токен обновления. Передается блокирующей функции, если выбран область действия offline_access.

Пример:

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

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

Yahoo

Когда пользователь входит в аккаунт Yahoo, передаются следующие учетные данные (без специальных параметров и областей действия):

  • Токен идентификатора
  • Токен доступа
  • Токен обновления

LinkedIn

Когда пользователь входит в аккаунт через LinkedIn, передаются следующие учетные данные:

  • Токен доступа

Apple

Когда пользователь входит в аккаунт с помощью Apple, передаются следующие учетные данные без специальных параметров или областей действия:

  • Токен идентификатора
  • Токен доступа
  • Токен обновления

Распространенные сценарии

Ниже приведены примеры распространенных случаев использования блокирующих функций.

Разрешить регистрацию только из определенного домена.

В следующем примере показано, как запретить регистрацию в приложении пользователям, не относящимся к домену example.com:

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (!user?.email?.includes('@example.com')) {
    throw new HttpsError(
      'invalid-argument', 'Unauthorized email');
  }
});

Python

 @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",
           )

Как запретить регистрацию пользователям с неподтвержденными адресами электронной почты

В следующем примере показано, как запретить регистрацию в приложении пользователям с непроверенными адресами электронной почты:

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (user.email && !user.emailVerified) {
    throw new HttpsError(
      'invalid-argument', 'Unverified email');
  }
});

Python

@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.",
        )

Как некоторые письма от поставщиков идентификационной информации считаются подтвержденными

В примере ниже показано, как считать проверенными адреса электронной почты пользователей от определенных поставщиков идентификационной информации:

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (user.email && !user.emailVerified && event.eventType.includes(':facebook.com')) {
    return {
      emailVerified: true,
    };
  }
});

Python

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

блокировка входа с определенных IP-адресов;

В следующем примере показано, как заблокировать вход с определенных диапазонов IP-адресов:

Node.js

export const beforesignedin = beforeUserSignedIn((event) => {
  if (isSuspiciousIpAddress(event.ipAddress)) {
    throw new HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});

Python

@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."
        )

Как задавать утверждения для сеанса и специальные утверждения

В следующем примере показано, как задать собственные утверждения и утверждения сеанса:

Node.js

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

Python

@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"],
            }
        )

Отслеживание IP-адресов для мониторинга подозрительной активности

Чтобы предотвратить кражу токена, отслеживайте IP-адрес, с которого пользователь выполнил вход, и сравнивайте его с IP-адресом в последующих запросах. Если запрос выглядит подозрительно, например IP-адреса относятся к разным географическим регионам, вы можете попросить пользователя войти в аккаунт ещё раз.

  1. Используйте утверждения сеанса, чтобы отслеживать IP-адрес, с которого пользователь выполняет вход:

    Node.js

    export const beforesignedin = beforeUserSignedIn((event) => {
      return {
        sessionClaims: {
          signInIpAddress: event.ipAddress,
        },
      };
    });
    

    Python

    @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. Когда пользователь пытается получить доступ к ресурсам, для которых требуется аутентификация с помощью Firebase Authentication, сравните IP-адрес в запросе с IP-адресом, использованным для входа:

    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!' })
          });
        }
      });
    });
    

    Python

    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)
    

Проверка фотографий пользователей

В примере ниже показано, как очистить фотографии профилей пользователей:

Node.js

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

Python

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

Чтобы узнать больше о том, как обнаруживать и удалять конфиденциальную информацию на изображениях, ознакомьтесь с документацией по Cloud Vision.

Доступ к учетным данным OAuth поставщика идентификационной информации пользователя

В следующем примере показано, как получить токен обновления для пользователя, вошедшего в аккаунт Google, и использовать его для вызова Google Календаря API. Токен обновления хранится для офлайн-доступа.

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

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

Python

@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.value, "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)

Переопределение вердикта reCAPTCHA Enterprise для операции пользователя

В примере ниже показано, как переопределить вердикт reCAPTCHA Enterprise для поддерживаемых сценариев взаимодействия с пользователем.

Подробнее о том, как интегрировать reCAPTCHA Enterprise с Firebase Authentication…

Блокирующие функции можно использовать, чтобы разрешать или блокировать процессы на основе заданных факторов, переопределяя результат, предоставленный reCAPTCHA Enterprise.

Node.js

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',
 }
});