Điều kiện kích hoạt chặn xác thực


Hàm chặn cho phép bạn thực thi mã tuỳ chỉnh để sửa đổi kết quả của việc người dùng đăng ký hoặc đăng nhập vào ứng dụng của bạn. Ví dụ: bạn có thể ngăn người dùng xác thực nếu họ không đáp ứng một số tiêu chí nhất định hoặc cập nhật thông tin của người dùng trước khi trả về thông tin đó cho ứng dụng khách.

Trước khi bắt đầu

Để sử dụng các hàm chặn, bạn phải nâng cấp dự án Firebase của mình lên Firebase Authentication with Identity Platform. Nếu bạn chưa nâng cấp, hãy làm việc này trước.

Tìm hiểu về hàm chặn

Bạn có thể đăng ký các hàm chặn cho những sự kiện sau:

  • Trước khi người dùng được tạo: Kích hoạt trước khi người dùng mới được lưu vào cơ sở dữ liệu Firebase Authentication và trước khi mã thông báo được trả về ứng dụng khách của bạn.

  • Trước khi người dùng đăng nhập: Kích hoạt sau khi thông tin đăng nhập của người dùng được xác minh, nhưng trước khi Firebase Authentication trả về mã thông báo nhận dạng cho ứng dụng khách của bạn. Nếu ứng dụng của bạn sử dụng tính năng xác thực đa yếu tố, thì hàm này sẽ kích hoạt sau khi người dùng xác minh yếu tố thứ hai. Xin lưu ý rằng việc tạo người dùng mới cũng kích hoạt cả hai sự kiện này.

  • Trước khi gửi email (chỉ dành cho Node.js): Kích hoạt trước khi gửi email (ví dụ: email đăng nhập hoặc đặt lại mật khẩu) cho người dùng.

  • Trước khi gửi tin nhắn SMS (chỉ dành cho Node.js): Kích hoạt trước khi gửi tin nhắn SMS cho người dùng, trong các trường hợp như xác thực đa yếu tố.

Hãy lưu ý những điều sau khi sử dụng hàm chặn:

  • Hàm của bạn phải phản hồi trong vòng 7 giây. Sau 7 giây, Firebase Authentication sẽ trả về lỗi và thao tác của ứng dụng khách không thành công.

  • Các mã phản hồi HTTP khác ngoài 200 sẽ được chuyển đến ứng dụng khách của bạn. Đảm bảo mã ứng dụng của bạn xử lý mọi lỗi mà hàm có thể trả về.

  • Hàm áp dụng cho tất cả người dùng trong dự án của bạn, bao gồm cả người dùng trong một đối tượng thuê. Firebase Authentication cung cấp thông tin về người dùng cho hàm của bạn, bao gồm cả mọi đối tượng thuê mà họ thuộc về, để bạn có thể phản hồi cho phù hợp.

  • Việc liên kết một nhà cung cấp danh tính khác với một tài khoản sẽ kích hoạt lại mọi hàm beforeUserSignedIn đã đăng ký.

  • Phương thức xác thực ẩn danh và tuỳ chỉnh không kích hoạt các hàm chặn.

Triển khai hàm chặn

Để chèn mã tuỳ chỉnh vào quy trình xác thực người dùng, hãy triển khai các hàm chặn. Sau khi triển khai các hàm chặn, mã tuỳ chỉnh của bạn phải hoàn tất thành công để quá trình xác thực và tạo người dùng thành công.

Bạn triển khai hàm chặn giống như cách bạn triển khai bất kỳ hàm nào. (xem trang Cloud Functions Bắt đầu để biết thông tin chi tiết). Tóm lại:

  1. Viết một hàm xử lý sự kiện được nhắm mục tiêu.

    Ví dụ: để bắt đầu, bạn có thể thêm một hàm không hoạt động như sau vào nguồn của mình:

    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
    

    Ví dụ trên đã bỏ qua việc triển khai logic xác thực tuỳ chỉnh. Hãy xem các phần sau để tìm hiểu cách triển khai hàm chặn và Các trường hợp phổ biến để biết các ví dụ cụ thể.

  2. Triển khai các hàm bằng CLI Firebase:

    firebase deploy --only functions
    

    Bạn phải triển khai lại các hàm mỗi khi cập nhật.

Lấy thông tin người dùng và bối cảnh

Sự kiện chặn cung cấp một đối tượng AuthBlockingEvent chứa thông tin về người dùng đang đăng nhập. Hãy sử dụng các giá trị này trong mã của bạn để xác định xem có cho phép một thao tác tiếp tục hay không.

Đối tượng này chứa các thuộc tính sau:

Tên Mô tả Ví dụ
locale Ngôn ngữ của ứng dụng. Bạn có thể đặt ngôn ngữ bằng cách sử dụng SDK ứng dụng hoặc bằng cách truyền tiêu đề ngôn ngữ trong API REST. fr hoặc sv-SE
ipAddress Địa chỉ IP của thiết bị mà người dùng cuối đang dùng để đăng ký hoặc đăng nhập. 114.14.200.1
userAgent Tác nhân người dùng kích hoạt chức năng chặn. Mozilla/5.0 (X11; Linux x86_64)
eventId Giá trị nhận dạng riêng biệt của sự kiện. rWsyPtolplG2TBFoOkkgyg
eventType Loại sự kiện. Thông tin này cung cấp thông tin về tên sự kiện, chẳng hạn như beforeSignIn hoặc beforeCreate, và phương thức đăng nhập liên quan được sử dụng, chẳng hạn như Google hoặc email/mật khẩu. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Luôn USER. USER
resource Dự án hoặc đối tượng thuê Firebase Authentication. projects/project-id/tenants/tenant-id
timestamp Thời gian sự kiện được kích hoạt, được định dạng dưới dạng chuỗi RFC 3339. Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Một đối tượng chứa thông tin về người dùng. AdditionalUserInfo
credential Một đối tượng chứa thông tin về thông tin xác thực của người dùng. AuthCredential

Chặn đăng ký hoặc đăng nhập

Để chặn một lượt đăng ký hoặc đăng nhập, hãy gửi một HttpsError trong hàm của bạn. Ví dụ:

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)

Bạn cũng có thể chỉ định thông báo lỗi tuỳ chỉnh:

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

Ví dụ sau đây cho thấy cách chặn người dùng không thuộc một miền cụ thể đăng ký ứng dụng của bạn:

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

Bất kể bạn sử dụng thông báo mặc định hay tuỳ chỉnh, Cloud Functions sẽ gói lỗi và trả về cho ứng dụng dưới dạng lỗi nội bộ. Ví dụ:

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

Ứng dụng của bạn nên phát hiện lỗi và xử lý lỗi đó cho phù hợp. Ví dụ:

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

Sửa đổi người dùng

Thay vì chặn một lượt đăng ký hoặc đăng nhập, bạn có thể cho phép thao tác tiếp tục, nhưng sửa đổi đối tượng User được lưu vào cơ sở dữ liệu của Firebase Authentication và được trả về ứng dụng.

Để sửa đổi người dùng, hãy trả về một đối tượng từ trình xử lý sự kiện chứa các trường cần sửa đổi. Bạn có thể sửa đổi các trường sau:

  • displayName
  • disabled
  • emailVerified
  • photoURL
  • customClaims
  • sessionClaims (chỉ beforeUserSignedIn)

Ngoại trừ sessionClaims, tất cả các trường đã sửa đổi đều được lưu vào cơ sở dữ liệu của Firebase Authentication, tức là các trường này được đưa vào mã thông báo phản hồi và duy trì giữa các phiên của người dùng.

Ví dụ sau đây cho thấy cách đặt tên hiển thị mặc định:

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

Nếu bạn đăng ký trình xử lý sự kiện cho cả beforeUserCreated và beforeUserSignedIn, hãy lưu ý rằng beforeUserSignedIn sẽ thực thi sau beforeUserCreated. Các trường người dùng được cập nhật trong beforeUserCreated sẽ xuất hiện trong beforeUserSignedIn. Nếu bạn đặt một trường khác với sessionClaims trong cả hai trình xử lý sự kiện, thì giá trị được đặt trong beforeUserSignedIn sẽ ghi đè giá trị được đặt trong beforeUserCreated. Chỉ đối với sessionClaims, các thuộc tính này được truyền đến các thông báo xác nhận quyền sở hữu mã thông báo của phiên hiện tại, nhưng không được duy trì hoặc lưu trữ trong cơ sở dữ liệu.

Ví dụ: nếu bạn đặt bất kỳ sessionClaims nào, thì beforeUserSignedIn sẽ trả về các thông tin đó cùng với mọi thông báo xác nhận quyền sở hữu beforeUserCreated và các thông tin này sẽ được hợp nhất. Khi hợp nhất, nếu một khoá sessionClaims khớp với một khoá trong customClaims, thì khoá customClaims khớp sẽ bị ghi đè trong các thông báo xác nhận quyền sở hữu mã thông báo bằng khoá sessionClaims. Tuy nhiên, khoá customClaims bị ghi đè vẫn sẽ được lưu trữ trong cơ sở dữ liệu cho các yêu cầu trong tương lai.

Thông tin đăng nhập và dữ liệu OAuth được hỗ trợ

Bạn có thể truyền thông tin xác thực và dữ liệu OAuth đến các hàm chặn từ nhiều nhà cung cấp danh tính. Bảng sau đây cho biết thông tin xác thực và dữ liệu nào được hỗ trợ cho từng nhà cung cấp danh tính:

Nhà cung cấp nhận dạng Mã thông báo nhận dạng Mã truy cập Thời gian hết hạn Mã thông báo bí mật Mã làm mới Xác nhận quyền sở hữu khi đăng nhập
Google Có Có Có Không Có Không
Facebook Không Có Có Không Không Không
Twitter Không Có Không Có Không Không
GitHub Không Có Không Không Không Không
Microsoft Có Có Có Không Có Không
LinkedIn Không Có Có Không Không Không
Yahoo Có Có Có Không Có Không
Apple Có Có Có Không Có Không
SAML Không Không Không Không Không Có
OIDC Có Có Có Không Có Có

Mã thông báo OAuth

Để sử dụng mã thông báo giá trị nhận dạng, mã truy cập hoặc mã làm mới trong một hàm chặn, trước tiên, bạn phải chọn hộp đánh dấu trên trang Hàm chặn của bảng điều khiển Firebase (chuyển đến thẻ Bảo mật > Xác thực > Cài đặt).

Mã làm mới sẽ không được trả về bởi bất kỳ nhà cung cấp danh tính nào khi đăng nhập trực tiếp bằng thông tin xác thực OAuth, chẳng hạn như mã thông báo nhận dạng hoặc mã truy cập. Trong trường hợp này, cùng một thông tin xác thực OAuth phía máy khách sẽ được chuyển đến hàm chặn.

Các phần sau đây mô tả từng loại nhà cung cấp danh tính cũng như thông tin xác thực và dữ liệu mà họ hỗ trợ.

Nhà cung cấp OIDC chung

Khi người dùng đăng nhập bằng một nhà cung cấp OIDC chung, các thông tin xác thực sau đây sẽ được chuyển:

  • Mã thông báo nhận dạng: Được cung cấp nếu bạn chọn quy trình id_token.
  • Mã truy cập: Được cung cấp nếu bạn chọn quy trình mã. Xin lưu ý rằng quy trình mã hiện chỉ được hỗ trợ thông qua API REST.
  • Mã làm mới: Được cung cấp nếu bạn chọn phạm vi offline_access.

Ví dụ:

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

Google

Khi người dùng đăng nhập bằng Google, các thông tin đăng nhập sau đây sẽ được chuyển:

  • Mã thông báo giá trị nhận dạng
  • Mã truy cập
  • Mã làm mới: Chỉ được cung cấp nếu các tham số tuỳ chỉnh sau đây được yêu cầu:
    • access_type=offline
    • prompt=consent, nếu người dùng đã đồng ý trước đó và không có phạm vi mới nào được yêu cầu

Ví dụ:

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

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

Tìm hiểu thêm về mã làm mới của Google.

Facebook

Khi người dùng đăng nhập bằng Facebook, thông tin xác thực sau đây sẽ được truyền:

  • Mã truy cập: Mã truy cập được trả về có thể được đổi lấy một mã truy cập khác. Tìm hiểu thêm về các loại mã truy cập khác nhau mà Facebook hỗ trợ và cách bạn có thể đổi các mã này lấy mã truy cập dài hạn.

GitHub

Khi người dùng đăng nhập bằng GitHub, thông tin xác thực sau đây sẽ được truyền:

  • Mã truy cập: Không hết hạn trừ phi bị thu hồi.

Microsoft

Khi người dùng đăng nhập bằng Microsoft, thông tin đăng nhập sau đây sẽ được chuyển:

  • Mã thông báo giá trị nhận dạng
  • Mã truy cập
  • Mã làm mới: Được truyền đến hàm chặn nếu bạn chọn phạm vi offline_access.

Ví dụ:

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

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

Yahoo

Khi người dùng đăng nhập bằng Yahoo, thông tin đăng nhập sau đây sẽ được chuyển mà không có bất kỳ phạm vi hoặc tham số tuỳ chỉnh nào:

  • Mã thông báo giá trị nhận dạng
  • Mã truy cập
  • Mã làm mới

LinkedIn

Khi người dùng đăng nhập bằng LinkedIn, thông tin xác thực sau đây sẽ được chuyển:

  • Mã truy cập

Apple

Khi người dùng đăng nhập bằng Apple, thông tin đăng nhập sau đây sẽ được chuyển mà không có bất kỳ phạm vi hoặc tham số tuỳ chỉnh nào:

  • Mã thông báo giá trị nhận dạng
  • Mã truy cập
  • Mã làm mới

Các trường hợp phổ biến

Các ví dụ sau đây minh hoạ một số trường hợp sử dụng phổ biến cho các hàm chặn:

Chỉ cho phép đăng ký từ một miền cụ thể

Ví dụ sau đây cho thấy cách ngăn người dùng không thuộc miền example.com đăng ký ứng dụng của bạn:

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

Chặn người dùng có email chưa được xác minh đăng ký

Ví dụ sau đây cho thấy cách ngăn người dùng có email chưa được xác minh đăng ký ứng dụng của bạn:

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

Coi một số email của nhà cung cấp danh tính là đã xác minh

Ví dụ sau đây cho thấy cách coi email của người dùng từ một số nhà cung cấp danh tính nhất định là đã xác minh:

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)

Chặn đăng nhập từ một số địa chỉ IP nhất định

Ví dụ sau đây minh hoạ cách chặn hoạt động đăng nhập từ một số dải địa chỉ 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."
        )

Đặt thông báo xác nhận tuỳ chỉnh và thông báo xác nhận phiên

Ví dụ sau đây cho thấy cách đặt thông báo xác nhận tuỳ chỉnh và thông báo xác nhận phiên:

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

Theo dõi địa chỉ IP để giám sát hoạt động đáng ngờ

Bạn có thể ngăn chặn hành vi đánh cắp mã thông báo bằng cách theo dõi địa chỉ IP mà người dùng đăng nhập, và so sánh địa chỉ đó với địa chỉ IP trong các yêu cầu tiếp theo. Nếu yêu cầu có vẻ đáng ngờ (ví dụ: IP đến từ các khu vực địa lý khác nhau), bạn có thể yêu cầu người dùng đăng nhập lại.

  1. Sử dụng thông báo xác nhận quyền sở hữu phiên để theo dõi địa chỉ IP mà người dùng dùng để đăng nhập:

    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. Khi người dùng cố gắng truy cập vào các tài nguyên yêu cầu xác thực bằng Firebase Authentication, hãy so sánh địa chỉ IP trong yêu cầu với IP dùng để đăng nhập:

    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)
    

Đang sàng lọc ảnh của người dùng

Ví dụ sau đây cho thấy cách dọn dẹp ảnh hồ sơ của người dùng:

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)

Để tìm hiểu thêm về cách phát hiện và dọn dẹp hình ảnh, hãy xem tài liệu về Cloud Vision.

Truy cập vào thông tin đăng nhập OAuth của nhà cung cấp danh tính của người dùng

Ví dụ sau đây minh hoạ cách lấy mã làm mới cho người dùng đã đăng nhập bằng Google và sử dụng mã đó để gọi API Lịch Google. Mã làm mới được lưu trữ để truy cập khi không có mạng.

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)

Ghi đè kết quả của reCAPTCHA Enterprise cho thao tác của người dùng

Ví dụ sau đây cho thấy cách ghi đè kết quả đánh giá của reCAPTCHA Enterprise cho các luồng người dùng được hỗ trợ.

Tham khảo bài viết Bật reCAPTCHA Enterprise để tìm hiểu thêm về cách tích hợp reCAPTCHA Enterprise với Xác thực Firebase.

Bạn có thể dùng các hàm chặn để cho phép hoặc chặn các luồng dựa trên các yếu tố tuỳ chỉnh, từ đó ghi đè kết quả do reCAPTCHA Enterprise cung cấp.

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