Расширьте аутентификацию Firebase с помощью функций блокировки


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

Прежде чем начать

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

Понимание блокирующих функций

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

  • Перед созданием пользователя : срабатывает до сохранения нового пользователя в базе данных Firebase Authentication и до возврата токена в клиентское приложение.

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

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

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

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

  • Функция должна ответить в течение 7 секунд. По истечении 7 секунд 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;
    });
    

    Питон

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

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

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

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

Питон

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

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

Node.js

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

Питон

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

Питон

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

Питон

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

Питон

@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 , соответствующие customClaims будут перезаписаны в утверждениях токена ключом sessionClaims . Однако перезаписанный ключ customClaims по-прежнему будет сохранен в базе данных для будущих запросов.

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

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

Поставщик удостоверений Идентификационный токен Токен доступа Время истечения срока действия Секрет токена Обновить токен Заявки на вход
Google Да Да Да Нет Да Нет
Фейсбук Нет Да Да Нет Нет Нет
Твиттер Нет Да Нет Да Нет Нет
GitHub Нет Да Нет Нет Нет Нет
Майкрософт Да Да Да Нет Да Нет
LinkedIn Нет Да Да Нет Нет Нет
Яху Да Да Да Нет Да Нет
Яблоко Да Да Да Нет Да Нет
САМЛ Нет Нет Нет Нет Нет Да
ОИДК Да Да Да Нет Да Да

OAuth-токены

Чтобы использовать токен идентификатора, токен доступа или токен обновления в функции блокировки, необходимо сначала установить флажок на странице «Функции блокировки» консоли Firebase .

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

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

Поставщики 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, и о том, как обменять их на долгосрочные токены .

GitHub

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

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

Майкрософт

При входе пользователя в систему 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, следующие учетные данные будут переданы без каких-либо пользовательских параметров или областей:

  • Идентификационный токен
  • Токен доступа
  • Обновить токен

LinkedIn

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

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

Яблоко

Когда пользователь входит в систему с помощью 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');
  }
});

Питон

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

Питон

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

Питон

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

Питон

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

Питон

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

    Питон

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

    Питон

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

Питон

@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, и использовать его для вызова API Google Календаря. Токен обновления сохраняется для доступа в автономном режиме.

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

Питон

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

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

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

Дополнительную информацию об интеграции reCAPTCHA Enterprise с аутентификацией Firebase см. в разделе Включение reCAPTCHA Enterprise.

Функции блокировки можно использовать для разрешения или блокировки потоков на основе пользовательских факторов, тем самым переопределяя результат, предоставленный 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',
 }
});