運用封鎖函式擴充 Firebase 驗證


封鎖函式可讓你執行自訂程式碼,藉此修改使用者註冊或登入應用程式的結果。舉例來說,如果使用者不符合特定條件,您可以禁止他們通過驗證,也可以在將使用者資訊傳回用戶端應用程式前更新資訊。

事前準備

如要使用封鎖函式,請將 Firebase 專案升級至 Firebase Authentication with Identity Platform。如果尚未升級,請先升級。

瞭解封鎖函式

您可以為下列事件註冊封鎖函式:

  • 使用者建立前:在系統將新使用者儲存至 Firebase Authentication 資料庫,以及將權杖傳回用戶端應用程式前觸發。

  • 使用者登入前:在系統驗證使用者憑證後觸發,但 Firebase Authentication 尚未將 ID 權杖傳回給用戶端應用程式。如果應用程式使用多重要素驗證,則會在使用者驗證第二個要素後觸發函式。請注意,建立新使用者也會觸發這兩個事件。

  • 傳送電子郵件前 (僅限 Node.js):在系統傳送電子郵件 (例如登入或密碼重設電子郵件) 給使用者前觸發。

  • 傳送簡訊前 (僅限 Node.js):在系統傳送簡訊給使用者前觸發,適用於多重驗證等情況。

使用封鎖函式時,請注意下列事項:

  • 函式必須在 7 秒內回覆,7 秒後,Firebase Authentication 會傳回錯誤,用戶端作業也會失敗。

  • 系統會將 200 以外的 HTTP 回應碼傳遞至用戶端應用程式。請確保用戶端程式碼會處理函式可能傳回的任何錯誤。

  • 函式會套用至專案中的所有使用者,包括租戶中的使用者。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 CLI 部署函式:

    firebase deploy --only functions
    

    每次更新函式後,都必須重新部署。

取得使用者和內容資訊

封鎖事件會提供 AuthBlockingEvent 物件,其中包含使用者登入的相關資訊。您可以在程式碼中使用這些值,判斷是否允許作業繼續進行。

這個物件包含下列屬性:

名稱 說明 範例
locale 應用程式語言代碼。您可以使用用戶端 SDK 設定語言代碼,或在 REST API 中傳遞語言代碼標頭。 frsv-SE
ipAddress 使用者註冊或登入裝置的 IP 位址。 114.14.200.1
userAgent 觸發封鎖函式的使用者代理程式。 Mozilla/5.0 (X11; Linux x86_64)
eventId 活動的專屬 ID。 rWsyPtolplG2TBFoOkkgyg
eventType 事件類型。這項資訊會提供事件名稱,例如 beforeSignInbeforeCreate,以及相關聯的登入方式,例如 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.
    }
}

修改使用者

您可以允許作業繼續進行,但修改儲存至 Firebase Authentication 資料庫並傳回給用戶端的 User 物件,而不是封鎖註冊或登入嘗試。

如要修改使用者,請從事件處理常式傳回包含要修改欄位的物件。您可以修改下列欄位:

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

如果您同時為 beforeUserCreatedbeforeUserSignedIn 註冊事件處理常式,請注意 beforeUserSignedIn 會在 beforeUserCreated 之後執行。在 beforeUserCreated 中更新的使用者欄位會顯示在 beforeUserSignedIn 中。如果您在兩個事件處理常式中設定 sessionClaims 以外的欄位,則 beforeUserSignedIn 中設定的值會覆寫 beforeUserCreated 中設定的值。如果是 sessionClaims,系統只會將這些屬性傳播至目前工作階段的權杖聲明,但不會保留或儲存在資料庫中。

舉例來說,如果設定了任何 sessionClaimsbeforeUserSignedIn 會連同任何 beforeUserCreated 聲明一併傳回,並合併這些聲明。合併時,如果 sessionClaims 鍵與 customClaims 中的鍵相符,則權杖聲明中的相符 customClaims 會由 sessionClaims 鍵覆寫。不過,覆寫的 customClaims 金鑰仍會保留在資料庫中,以供日後要求使用。

支援的 OAuth 憑證和資料

您可以將 OAuth 憑證和資料從各種身分識別供應商傳遞至封鎖函式。下表列出各身分識別提供者支援的憑證和資料:

識別資訊提供者 ID 權杖 存取權杖 到期時間 權杖密鑰 更新權杖 登入聲明
Google
Facebook
Twitter
GitHub
Microsoft
LinkedIn
Yahoo
Apple
SAML
OIDC

OAuth 權杖

如要在封鎖函式中使用 ID 權杖、存取權杖或重新整理權杖,請先在 Firebase 控制台的「封鎖函式」頁面中選取核取方塊。

直接使用 OAuth 憑證 (例如 ID 權杖或存取權杖) 登入時,任何身分識別提供者都不會傳回更新權杖。在這種情況下,系統會將相同的用戶端 OAuth 憑證傳遞至封鎖函式。

以下各節將說明各身分識別提供者類型,以及支援的憑證和資料。

一般 OIDC 提供者

使用者透過一般 OIDC 提供者登入時,系統會傳遞下列憑證:

  • ID 權杖:如果選取 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 登入時,系統會傳遞下列憑證:

  • ID 權杖
  • 存取權杖
  • 重新整理權杖:只有在要求下列自訂參數時才會提供:
    • 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 登入時,系統會傳遞下列憑證:

  • ID 權杖
  • 存取權杖
  • 刷新權杖:如果選取 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 登入時,系統會傳遞下列憑證,不含任何自訂參數或範圍:

  • ID 權杖
  • 存取權杖
  • 更新權杖

LinkedIn

使用者透過 LinkedIn 登入時,系統會傳遞下列憑證:

  • 存取權杖

Apple

使用者透過 Apple 登入時,系統會傳遞下列憑證,不含任何自訂參數或範圍:

  • ID 權杖
  • 存取權杖
  • 更新權杖

常見情境

下列範例說明封鎖函式的一些常見用途:

只允許特定網域註冊

以下範例說明如何防止不屬於 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 Calendar 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,
                                             "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」。

封鎖函式可用於根據自訂因素允許或封鎖流程,藉此覆寫 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',
 }
});