محرک های مسدودکننده تأیید اعتبار


توابع مسدودکننده به شما امکان می‌دهند کد سفارشی را اجرا کنید که نتیجه ثبت نام یا ورود کاربر به برنامه شما را تغییر می‌دهد. به عنوان مثال، می‌توانید در صورت عدم رعایت معیارهای خاص، از احراز هویت کاربر جلوگیری کنید، یا اطلاعات کاربر را قبل از بازگرداندن آن به برنامه کلاینت خود به‌روزرسانی کنید.

قبل از اینکه شروع کنی

برای استفاده از توابع مسدودکننده، باید پروژه Firebase خود را به Firebase Authentication with Identity Platform ارتقا دهید. اگر قبلاً آن را ارتقا نداده‌اید، ابتدا این کار را انجام دهید.

درک توابع مسدود کننده

می‌توانید توابع مسدودکننده را برای این رویدادها ثبت کنید:

  • قبل از ایجاد کاربر : قبل از ذخیره کاربر جدید در پایگاه داده Firebase Authentication و قبل از بازگرداندن توکن به برنامه کلاینت شما، فعال می‌شود.

  • قبل از ورود کاربر : پس از تأیید اعتبار کاربر، اما قبل از اینکه Firebase Authentication یک توکن شناسه را به برنامه کلاینت شما برگرداند، فعال می‌شود. اگر برنامه شما از احراز هویت چند عاملی استفاده می‌کند، این تابع پس از تأیید عامل دوم توسط کاربر فعال می‌شود. توجه داشته باشید که ایجاد یک کاربر جدید نیز هر دوی این رویدادها را فعال می‌کند.

  • قبل از ارسال ایمیل (فقط Node.js) : قبل از ارسال ایمیل فعال می‌شود (برای مثال،
    ایمیل ورود به سیستم یا بازنشانی رمز عبور) برای کاربر ارسال می‌شود.

  • قبل از ارسال پیامک (فقط Node.js) : قبل از ارسال پیامک به کاربر، برای مواردی مانند احراز هویت چند عاملی، فعال می‌شود.

هنگام استفاده از توابع مسدود کننده، موارد زیر را در نظر داشته باشید:

  • تابع شما باید ظرف ۷ ثانیه پاسخ دهد. پس از ۷ ثانیه، Firebase Authentication خطایی برمی‌گرداند و عملیات کلاینت با شکست مواجه می‌شود.

  • کدهای پاسخ HTTP غیر از 200 به برنامه‌های کلاینت شما ارسال می‌شوند. مطمئن شوید که کد کلاینت شما هرگونه خطایی را که تابع شما می‌تواند برگرداند، مدیریت می‌کند.

  • توابع برای همه کاربران پروژه شما، از جمله هر کاربر موجود در یک مستاجر، اعمال می‌شوند. Firebase Authentication اطلاعاتی در مورد کاربران تابع شما، از جمله هر مستاجری که به آن تعلق دارند، ارائه می‌دهد، بنابراین می‌توانید بر اساس آن پاسخ دهید.

  • پیوند دادن یک ارائه‌دهنده هویت دیگر به یک حساب کاربری، هرگونه تابع ثبت‌شده‌ی beforeUserSignedIn را دوباره فعال می‌کند.

  • احراز هویت ناشناس و سفارشی، توابع مسدودسازی را فعال نمی‌کنند.

یک تابع مسدودکننده مستقر کنید

برای وارد کردن کد سفارشی خود در جریان‌های احراز هویت کاربر، توابع مسدودکننده را مستقر کنید. پس از استقرار توابع مسدودکننده، کد سفارشی شما باید با موفقیت تکمیل شود تا احراز هویت و ایجاد کاربر با موفقیت انجام شود.

شما یک تابع مسدودکننده را به همان روشی که هر تابع دیگری را مستقر می‌کنید، مستقر می‌کنید. (برای جزئیات بیشتر به صفحه شروع به کار Cloud Functions مراجعه کنید.) به طور خلاصه:

  1. تابعی بنویسید که رویداد مورد نظر را مدیریت کند.

    برای مثال، برای شروع، می‌توانید یک تابع no-op مانند زیر به منبع خود اضافه کنید:

    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 یا email/password، ارائه می‌دهد. 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 در تابع خود قرار دهید. برای مثال:

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

مثال زیر نحوه مسدود کردن کاربرانی که در یک دامنه خاص نیستند را از ثبت نام در برنامه شما نشان می‌دهد:

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 خطا را پوشش می‌دهند و آن را به عنوان یک خطای داخلی به کلاینت برمی‌گردانند. برای مثال:

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

برنامه شما باید خطا را تشخیص دهد و بر اساس آن مدیریت کند. برای مثال:

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 ذخیره شده و به کلاینت بازگردانده می‌شود، تغییر دهید.

برای تغییر یک کاربر، یک شیء از event handler خود که حاوی فیلدهای مورد نظر برای تغییر است، برگردانید. می‌توانید فیلدهای زیر را تغییر دهید:

  • displayName
  • disabled
  • emailVerified
  • photoUrl
  • customClaims
  • sessionClaims (فقط beforeUserSignedIn )

به استثنای sessionClaims ، تمام فیلدهای اصلاح‌شده در پایگاه داده Firebase Authentication ذخیره می‌شوند، به این معنی که در توکن پاسخ گنجانده می‌شوند و بین جلسات کاربر باقی می‌مانند.

مثال زیر نحوه تنظیم نام نمایشی پیش‌فرض را نشان می‌دهد:

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 یک event handler ثبت کنید، توجه داشته باشید که beforeUserSignedIn پس از beforeUserCreated اجرا می‌شود. فیلدهای کاربری که در beforeUserCreated به‌روزرسانی شده‌اند، در beforeUserSignedIn قابل مشاهده هستند. اگر در هر دو رویداد handler، فیلدی غیر از sessionClaims تنظیم کنید، مقدار تنظیم شده در beforeUserSignedIn مقدار تنظیم شده در beforeUserCreated را بازنویسی می‌کند. فقط برای sessionClaims ، آنها به توکن‌های درخواستی جلسه فعلی منتقل می‌شوند، اما در پایگاه داده ذخیره یا نگهداری نمی‌شوند.

برای مثال، اگر هر sessionClaims تنظیم شده باشد، beforeUserSignedIn آنها را به همراه هرگونه Claims beforeUserCreated برمی‌گرداند و آنها ادغام می‌شوند. وقتی آنها ادغام می‌شوند، اگر یک کلید sessionClaims با کلیدی در customClaims مطابقت داشته باشد، customClaims منطبق در ادعاهای توکن توسط کلید sessionClaims بازنویسی می‌شود. با این حال، کلید customClaims overwitten همچنان برای درخواست‌های آینده در پایگاه داده باقی خواهد ماند.

اعتبارنامه‌ها و داده‌های OAuth پشتیبانی‌شده

شما می‌توانید اعتبارنامه‌ها و داده‌های OAuth را به توابع مسدودکننده از ارائه‌دهندگان هویت مختلف ارسال کنید. جدول زیر نشان می‌دهد که چه اعتبارنامه‌ها و داده‌هایی برای هر ارائه‌دهنده هویت پشتیبانی می‌شوند:

ارائه دهنده هویت توکن شناسایی توکن دسترسی زمان انقضا رمز توکن توکن تازه‌سازی ادعاهای ورود به سیستم
گوگل بله بله بله خیر بله خیر
فیسبوک خیر بله بله خیر خیر خیر
توییتر خیر بله خیر بله خیر خیر
گیت‌هاب خیر بله خیر خیر خیر خیر
مایکروسافت بله بله بله خیر بله خیر
لینکدین خیر بله بله خیر خیر خیر
یاهو بله بله بله خیر بله خیر
اپل بله بله بله خیر بله خیر
سامل خیر خیر خیر خیر خیر بله
اوییدک بله بله بله خیر بله بله

توکن‌های OAuth

برای استفاده از توکن شناسه، توکن دسترسی یا توکن به‌روزرسانی در یک تابع مسدودکننده، ابتدا باید کادر انتخاب را در صفحه توابع مسدودکننده کنسول Firebase انتخاب کنید.

هنگام ورود مستقیم به سیستم با یک اعتبارنامه OAuth، مانند یک توکن شناسه یا توکن دسترسی، هیچ ارائه‌دهنده هویتی توکن‌های Refresh را برنمی‌گرداند. در این شرایط، همان اعتبارنامه 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);

گوگل

وقتی کاربری با گوگل وارد سیستم می‌شود، اطلاعات زیر ارسال می‌شود:

  • توکن شناسایی
  • توکن دسترسی
  • نشانه به‌روزرسانی : فقط در صورتی ارائه می‌شود که پارامترهای سفارشی زیر درخواست شوند:
    • 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);

درباره توکن‌های به‌روزرسانی گوگل بیشتر بدانید.

فیسبوک

وقتی کاربری با فیس‌بوک وارد سیستم می‌شود، اطلاعات زیر منتقل می‌شود:

  • توکن دسترسی : یک توکن دسترسی برگردانده می‌شود که می‌تواند با توکن دسترسی دیگری مبادله شود. درباره انواع مختلف توکن‌های دسترسی پشتیبانی شده توسط فیس‌بوک و نحوه مبادله آنها با توکن‌های با طول عمر بالا بیشتر بدانید.

گیت‌هاب

وقتی کاربری با GitHub وارد سیستم می‌شود، اعتبارنامه زیر ارسال می‌شود:

  • توکن دسترسی : تا زمانی که لغو نشود، منقضی نمی‌شود.

مایکروسافت

وقتی کاربری با مایکروسافت وارد سیستم می‌شود، اطلاعات احراز هویت زیر ارسال می‌شود:

  • توکن شناسایی
  • توکن دسترسی
  • توکن به‌روزرسانی : در صورت انتخاب محدوده‌ی 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);

یاهو

وقتی کاربری با یاهو وارد سیستم می‌شود، اطلاعات احراز هویت زیر بدون هیچ پارامتر یا محدوده سفارشی ارسال می‌شود:

  • توکن شناسایی
  • توکن دسترسی
  • توکن تازه‌سازی

لینکدین

وقتی کاربری با لینکدین وارد سیستم می‌شود، اطلاعات زیر به او منتقل می‌شود:

  • توکن دسترسی

اپل

وقتی کاربری با اپل وارد سیستم می‌شود، اطلاعات احراز هویت زیر بدون هیچ پارامتر یا محدوده سفارشی ارسال می‌شود:

  • توکن شناسایی
  • توکن دسترسی
  • توکن تازه‌سازی

سناریوهای رایج

مثال‌های زیر برخی از موارد استفاده رایج برای توابع مسدودکننده را نشان می‌دهند:

فقط اجازه ثبت نام از یک دامنه خاص

مثال زیر نحوه جلوگیری از ثبت نام کاربرانی که عضو دامنه 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",
           )

مسدود کردن ثبت نام کاربران با ایمیل‌های تایید نشده

مثال زیر نحوه جلوگیری از ثبت نام کاربران با ایمیل‌های تأیید نشده در برنامه شما را نشان می‌دهد:

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

تلقی کردن ایمیل‌های خاص ارائه‌دهنده هویت به عنوان ایمیل‌های تأیید شده

مثال زیر نحوه برخورد با ایمیل‌های کاربر از ارائه‌دهندگان هویت خاص را به عنوان ایمیل‌های تأیید شده نشان می‌دهد:

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 خاص را نشان می‌دهد:

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

تنظیم ادعاهای سفارشی و جلسه‌ای

مثال زیر نحوه تنظیم custom و session claims را نشان می‌دهد:

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. از session claims برای ردیابی آدرس IP که کاربر با آن وارد سیستم می‌شود استفاده کنید:

    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)
    

نمایش عکس‌های کاربران

مثال زیر نحوه‌ی پاکسازی عکس‌های پروفایل کاربران را نشان می‌دهد:

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 ارائه‌دهنده هویت کاربر

مثال زیر نحوه دریافت یک توکن به‌روزرسانی برای کاربری که با حساب گوگل وارد سیستم شده است و استفاده از آن برای فراخوانی APIهای تقویم گوگل را نشان می‌دهد. توکن به‌روزرسانی برای دسترسی آفلاین ذخیره می‌شود.

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 را نادیده بگیرند.

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