ขยายการตรวจสอบสิทธิ์ Firebase ด้วยฟังก์ชันการบล็อก


ฟังก์ชันการบล็อกให้คุณใช้โค้ดที่กำหนดเองซึ่งจะแก้ไขผลลัพธ์จากการที่ผู้ใช้ลงทะเบียนหรือลงชื่อเข้าใช้แอปของคุณ เช่น คุณสามารถป้องกันไม่ให้ผู้ใช้ตรวจสอบสิทธิ์หากไม่เป็นไปตามเกณฑ์ที่กำหนด หรืออัปเดตข้อมูลของผู้ใช้ก่อนที่จะส่งกลับไปยังแอปไคลเอ็นต์

ก่อนเริ่มต้น

หากต้องการใช้ฟังก์ชันการบล็อก คุณต้องอัปเกรดโปรเจ็กต์ Firebase เป็น Firebase Authentication with Identity Platform หากยังไม่ได้อัปเกรด ให้อัปเกรดก่อน

ทําความเข้าใจฟังก์ชันการบล็อก

คุณสามารถลงทะเบียนฟังก์ชันการบล็อกสําหรับเหตุการณ์ต่อไปนี้

  • ก่อนสร้างผู้ใช้: ทริกเกอร์ก่อนที่ระบบจะบันทึกผู้ใช้ใหม่ลงในฐานข้อมูล Firebase Authentication และก่อนที่ระบบจะส่งคืนโทเค็นไปยังแอปไคลเอ็นต์

  • ก่อนที่ผู้ใช้จะลงชื่อเข้าใช้: ทริกเกอร์หลังจากยืนยันข้อมูลเข้าสู่ระบบของผู้ใช้แล้ว แต่ก่อนFirebase Authenticationจะแสดงผลโทเค็นระบุตัวตนไปยังแอปไคลเอ็นต์ หากแอปของคุณใช้การตรวจสอบสิทธิ์แบบหลายปัจจัย ฟังก์ชันจะทริกเกอร์หลังจากที่ผู้ใช้ยืนยันปัจจัยที่ 2 โปรดทราบว่าการสร้างผู้ใช้ใหม่จะทริกเกอร์ทั้ง 2 เหตุการณ์นี้ด้วย

  • ก่อนส่งอีเมล (Node.js เท่านั้น): ทริกเกอร์ก่อนส่งอีเมล (เช่น
    อีเมลลงชื่อเข้าใช้หรืออีเมลรีเซ็ตรหัสผ่าน) ไปยังผู้ใช้

  • ก่อนส่งข้อความ SMS (Node.js เท่านั้น): ทริกเกอร์ก่อนส่งข้อความ SMS ไปยังผู้ใช้สำหรับกรณีต่างๆ เช่น การตรวจสอบสิทธิ์แบบหลายปัจจัย

โปรดคำนึงถึงสิ่งต่อไปนี้เมื่อใช้ฟังก์ชันการบล็อก

  • ฟังก์ชันต้องตอบกลับภายใน 7 วินาที หลังจากผ่านไป 7 วินาที Firebase Authentication จะแสดงข้อผิดพลาด และการดำเนินการของไคลเอ็นต์จะล้มเหลว

  • ระบบจะส่งโค้ดการตอบกลับ HTTP ที่ไม่ใช่ 200 ไปยังแอปไคลเอ็นต์ ตรวจสอบว่าโค้ดไคลเอ็นต์จัดการข้อผิดพลาดที่ฟังก์ชันอาจแสดงผลได้

  • ฟังก์ชันจะมีผลกับผู้ใช้ทุกคนในโปรเจ็กต์ รวมถึงผู้ใช้ที่อยู่ในกลุ่มผู้ใช้ Firebase Authentication จะส่งข้อมูลเกี่ยวกับผู้ใช้ไปยังฟังก์ชันของคุณ รวมถึงข้อมูลเกี่ยวกับเทนนต์ที่ผู้ใช้เหล่านั้นเป็นเจ้าของ เพื่อให้คุณดำเนินการตามความเหมาะสม

  • การลิงก์ผู้ให้บริการข้อมูลประจำตัวรายอื่นกับบัญชีจะทริกเกอร์ฟังก์ชัน beforeUserSignedIn ที่ลงทะเบียนไว้อีกครั้ง

  • การตรวจสอบสิทธิ์แบบไม่ระบุตัวตนและแบบกำหนดเองจะไม่ทริกเกอร์ฟังก์ชันการบล็อก

ทำให้ฟังก์ชันการบล็อกใช้งานได้

หากต้องการแทรกโค้ดที่กําหนดเองลงในขั้นตอนการตรวจสอบสิทธิ์ผู้ใช้ ให้ติดตั้งใช้งานฟังก์ชันการบล็อก เมื่อติดตั้งใช้งานฟังก์ชันการบล็อกแล้ว โค้ดที่กำหนดเองต้องทำงานเสร็จสมบูรณ์เพื่อให้การตรวจสอบสิทธิ์และการสร้างผู้ใช้สําเร็จ

คุณทำให้ฟังก์ชันการบล็อกใช้งานได้ในลักษณะเดียวกับที่ใช้งานฟังก์ชันอื่นๆ (ดูรายละเอียดที่หน้าเริ่มต้นใช้งาน Cloud Functions) บทสรุปมีดังนี้:

  1. เขียนฟังก์ชันที่จัดการเหตุการณ์เป้าหมาย

    เช่น หากต้องการเริ่มต้นใช้งาน คุณสามารถเพิ่มฟังก์ชันที่ไม่มีการดำเนินการใดๆ ต่อไปนี้ลงในแหล่งที่มา

    Node.jsPython
    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 User Agent ที่ทริกเกอร์ฟังก์ชันการบล็อก 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 Python
import { HttpsError } from "firebase-functions/v2/identity";

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

นอกจากนี้ คุณยังระบุข้อความแสดงข้อผิดพลาดที่กำหนดเองได้ด้วย โดยทำดังนี้

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

ตัวอย่างต่อไปนี้แสดงวิธีบล็อกไม่ให้ผู้ใช้ที่ไม่ได้อยู่ในโดเมนที่เจาะจงลงทะเบียนใช้แอป

Node.js Python
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 Python
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 Python
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 ในตัวแฮนเดิลเหตุการณ์ทั้ง 2 รายการ ค่าที่กําหนดใน beforeUserSignedIn จะเขียนทับค่าที่กําหนดใน beforeUserCreated สำหรับ sessionClaims เท่านั้น ระบบจะนำไปเผยแพร่ไปยังการอ้างสิทธิ์โทเค็นของเซสชันปัจจุบัน แต่ไม่เก็บไว้หรือจัดเก็บในฐานข้อมูล

ตัวอย่างเช่น หากตั้งค่า sessionClaims ไว้ beforeUserSignedIn จะส่งผลลัพธ์ที่มีการอ้างสิทธิ์ beforeUserCreated รายการขึ้นมาและจะรวมเข้าด้วยกัน เมื่อผสานแล้ว หากคีย์ sessionClaims ตรงกับคีย์ใน customClaims ระบบจะเขียนทับ customClaims ที่ตรงกันในการอ้างสิทธิ์โทเค็นด้วยคีย์ sessionClaims อย่างไรก็ตาม คีย์ customClaims ที่เขียนทับจะยังคงอยู่ในฐานข้อมูลสําหรับคําขอในอนาคต

ข้อมูลเข้าสู่ระบบและข้อมูลที่รองรับของ OAuth

คุณสามารถส่งข้อมูลเข้าสู่ระบบ OAuth และข้อมูลไปยังฟังก์ชันการบล็อกจากผู้ให้บริการข้อมูลประจำตัวต่างๆ ได้ ตารางต่อไปนี้แสดงข้อมูลเข้าสู่ระบบและข้อมูลที่รองรับสำหรับผู้ให้บริการข้อมูลประจำตัวแต่ละราย

ผู้ให้บริการข้อมูลประจำตัว โทเค็นรหัส โทเค็นเพื่อการเข้าถึง เวลาหมดอายุ ข้อมูลลับของโทเค็น โทเค็นการรีเฟรช การอ้างสิทธิ์ในการลงชื่อเข้าใช้
Google ใช่ ใช่ ใช่ ไม่ใช่ ใช่ ไม่
Facebook ไม่ ใช่ ใช่ ไม่ได้ ไม่ใช่ ไม่
Twitter ไม่ ใช่ ไม่ใช่ ใช่ ไม่ได้ ไม่
GitHub ไม่ ใช่ ไม่ได้ ไม่ใช่ ไม่ใช่ ไม่
Microsoft ใช่ ใช่ ใช่ ไม่ใช่ ใช่ ไม่
LinkedIn ไม่ ใช่ ใช่ ไม่ได้ ไม่ใช่ ไม่
Yahoo ใช่ ใช่ ใช่ ไม่ใช่ ใช่ ไม่
Apple ใช่ ใช่ ใช่ ไม่ใช่ ใช่ ไม่
SAML ไม่ ไม่ใช่ ไม่ใช่ ไม่ใช่ ไม่ได้ ใช่
OIDC ใช่ ใช่ ใช่ ไม่ใช่ ใช่ ใช่

โทเค็น OAuth

หากต้องการใช้โทเค็นระบุตัวตน โทเค็นการเข้าถึง หรือโทเค็นรีเฟรชในฟังก์ชันการบล็อก คุณต้องเลือกช่องทําเครื่องหมายในหน้าฟังก์ชันการบล็อกของคอนโซล Firebase ก่อน

ผู้ให้บริการข้อมูลประจำตัวจะไม่แสดงโทเค็นรีเฟรชเมื่อลงชื่อเข้าใช้ด้วยข้อมูลเข้าสู่ระบบ OAuth โดยตรง เช่น โทเค็นระบุตัวตนหรือโทเค็นการเข้าถึง ในกรณีนี้ ระบบจะส่งข้อมูลเข้าสู่ระบบ 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

เมื่อผู้ใช้ลงชื่อเข้าใช้ด้วย 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 ระบบจะส่งข้อมูลเข้าสู่ระบบต่อไปนี้โดยไม่มีพารามิเตอร์หรือขอบเขตที่กำหนดเอง

  • โทเค็นรหัส
  • โทเค็นการเข้าถึง
  • โทเค็นการรีเฟรช

สถานการณ์ที่พบบ่อย

ตัวอย่างต่อไปนี้แสดง Use Case ที่พบบ่อยสำหรับฟังก์ชันการบล็อก

อนุญาตให้จดทะเบียนจากโดเมนที่เฉพาะเจาะจงเท่านั้น

ตัวอย่างต่อไปนี้แสดงวิธีป้องกันไม่ให้ผู้ใช้ที่ไม่ได้อยู่ในโดเมน example.com ลงทะเบียนกับแอปของคุณ

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

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

การบล็อกไม่ให้ผู้ใช้ที่มีอีเมลที่ยังไม่ยืนยันลงทะเบียน

ตัวอย่างต่อไปนี้แสดงวิธีป้องกันไม่ให้ผู้ใช้ที่มีอีเมลที่ยังไม่ได้รับการยืนยันลงทะเบียนกับแอป

Node.js Python
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 Python
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 Python
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 Python
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 Python
    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 ที่ใช้ลงชื่อเข้าใช้ ดังนี้

    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 Python
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 และใช้โทเค็นดังกล่าวเพื่อเรียกใช้ Google Calendar API ระบบจะจัดเก็บโทเค็นรีเฟรชไว้สำหรับการเข้าถึงแบบออฟไลน์

Node.js Python
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 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',
 }
});