گسترش «اصالت‌سنجی Firebase» با توابع مسدودکننده


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

قبل از شروع

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

آشنایی با توابع مسدودکننده

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

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

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

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

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

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

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

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

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

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

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

پیاده‌سازی تابع مسدودکننده

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

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

  1. تابعی بنویسید که رویداد هدف‌گذاری‌شده را مدیریت کند.

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

    Node.js

    import {
      beforeUserCreated,
    } from "firebase-functions/v2/identity";
    
    export const beforecreated = beforeUserCreated((event) => {
      // TODO
      return;
    });
    

    پایتون

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

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

  2. بااستفاده از Firebase CLI، توابع خود را مستقر کنید:

    firebase deploy --only functions
    

    هر بار که توابع خود را به‌روز می‌کنید، باید آن‌ها را دوباره مستقر کنید.

درحال دریافت اطلاعات کاربر و بافت

رویدادهای مسدودکننده یک AuthBlockingEvent شیء ارائه می‌دهند که حاوی اطلاعاتی درباره کاربر واردشده است. از این مقادیر در کدتان استفاده کنید تا تعیین کنید که آیا عملیات مجاز است ادامه یابد یا نه.

این شیء شامل دارایی‌های زیر است:

نام شرح مثال
locale منطقه زبانی برنامه. می‌توانید بوم را بااستفاده از «کیت توسعه نرم‌افزار کارخواه» یا با ارسال سرایند بوم در «میانای برنامه‌سازی کاربردی REST» تنظیم کنید. fr یا sv-SE
ipAddress نشانی IP دستگاهی که کاربر نهایی از آن ثبت‌نام یا به سیستم وارد می‌شود. 114.14.200.1
userAgent عامل کاربری که تابع مسدودسازی را راه‌اندازی می‌کند. Mozilla/5.0 (X11; Linux x86_64)
eventId شناسه یکتای رویداد. rWsyPtolplG2TBFoOkkgyg
eventType نوع رویداد. این بخش اطلاعاتی درباره نام رویداد، مثل beforeSignIn یا beforeCreate، و روش ورود به سیستم مرتبط استفاده‌شده، مثل Google یا ایمیل/گذرواژه، ارائه می‌دهد. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType همیشه USER. USER
resource پروژه یا مستأجر Firebase Authentication. projects/project-id/tenants/tenant-id
timestamp زمان راه‌اندازی رویداد، قالب‌بندی‌شده به‌عنوان رشته RFC 3339. Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo شیئی حاوی اطلاعات درباره کاربر. AdditionalUserInfo
credential شیئی حاوی اطلاعات درباره اطلاعات اعتباری کاربر. AuthCredential

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

برای مسدود کردن ثبت‌نام یا تلاش برای ورود به سیستم، HttpsError را در تابع خود قرار دهید. برای مثال:

Node.js

import { HttpsError } from "firebase-functions/v2/identity";

throw new HttpsError('invalid-argument');

پایتون

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

همچنین می‌توانید پیام خطای سفارشی مشخص کنید:

Node.js

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

پایتون

raise https_fn.HttpsError(
    code=https_fn.FunctionsErrorCode.PERMISSION_DENIED,
    message="Unauthorized request origin!"
)

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

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  // (If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from event.resource, e.g. 'projects/project-id/tenant/tenant-id-1')

  // Only users of a specific domain can sign up.
  if (!user?.email?.includes('@acme.com')) {
    throw new HttpsError('invalid-argument', "Unauthorized email");
  }
});

پایتون

# Block account creation with any non-acme email address.
@identity_fn.before_user_created()
def validatenewuser(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeCreateResponse | None:
    # User data passed in from the CloudEvent.
    user = event.data

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

صرف‌نظر از اینکه از پیام پیش‌فرض یا سفارشی استفاده می‌کنید، Cloud Functions خطا را می‌پیچد و آن را به‌عنوان خطای داخلی به کارخواه برمی‌گرداند. برای مثال:

Node.js

throw new HttpsError('invalid-argument', "Unauthorized email");

پایتون

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

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

جاوا اسکریپت

import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth';

// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
const auth = getAuth();
try {
  const result = await createUserWithEmailAndPassword(auth)
  const idTokenResult = await result.user.getIdTokenResult();
  console.log(idTokenResult.claim.admin);
} catch(error) {
  if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
      // Display error.
    } else {
      // Registration succeeds.
    }
}

درحال تغییر دادن کاربر

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

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

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

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

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

Node.js

export const beforecreated = beforeUserCreated((event) => {
  return {
    // If no display name is provided, set it to "Guest".
    displayName: event.data.displayName || 'Guest'
  };
});

پایتون

@identity_fn.before_user_created()
def setdefaultname(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeCreateResponse | None:
    return identity_fn.BeforeCreateResponse(
        # If no display name is provided, set it to "Guest".
        display_name=event.data.display_name if event.data.display_name is not None else "Guest"
    )

اگر کنترل‌کننده رویدادی را برای beforeUserCreated و beforeUserSignedIn ثبت کنید، توجه داشته باشید که beforeUserSignedIn پس‌از beforeUserCreated اجرا می‌شود. فیلدهای کاربر به‌روزرسانی‌شده در beforeUserCreated در beforeUserSignedIn قابل‌مشاهده است. اگر فیلدی غیر از sessionClaims را در هر دو مدیریت‌کننده رویداد تنظیم کنید، مقدار تنظیم‌شده در beforeUserSignedIn مقدار تنظیم‌شده در beforeUserCreated را ملغی می‌کند. فقط برای sessionClaims، این ادعاها به ادعاهای نشان جلسه فعلی منتقل می‌شوند، اما در پایگاه داده ذخیره یا ماندگار نمی‌شوند.

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

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

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

ارائه‌دهنده هویت شناسه داده‌واحد کد دسترسی زمان انقضا رمز کد ژتون بازآوری ادعاهای ورود به سیستم
Google بله بله بله نه بله نه
فیس‌بوک نه بله بله نه نه نه
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 به سیستم وارد می‌شود، اطلاعات اعتباری زیر بدون هیچ پارامتر یا حوزه سفارشی منتقل می‌شود:

  • داده‌واحد شناسه
  • کد دسترسی
  • ژتون بازآوری

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

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

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

مثال زیر نشان می‌دهد چگونه از ثبت‌نام کاربرانی که بخشی از دامنه example.com نیستند در برنامه خود جلوگیری کنید:

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (!user?.email?.includes('@example.com')) {
    throw new HttpsError(
      'invalid-argument', 'Unauthorized email');
  }
});

پایتون

 @identity_fn.before_user_created()
   def validatenewuser(
       event: identity_fn.AuthBlockingEvent,
   ) -> identity_fn.BeforeCreateResponse | None:
       # User data passed in from the CloudEvent.
       user = event.data

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

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

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

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (user.email && !user.emailVerified) {
    throw new HttpsError(
      'invalid-argument', 'Unverified email');
  }
});

پایتون

@identity_fn.before_user_created()
def requireverified(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeCreateResponse | None:
    if event.data.email is not None and not event.data.email_verified:
        raise https_fn.HttpsError(
            code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
            message="You must register using a trusted provider.",
        )

برخی‌از ایمیل‌های ارائه‌دهنده هویت را به‌عنوان درستی‌سنجی‌شده درنظر می‌گیریم

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

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (user.email && !user.emailVerified && event.eventType.includes(':facebook.com')) {
    return {
      emailVerified: true,
    };
  }
});

پایتون

@identity_fn.before_user_created()
def markverified(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeCreateResponse | None:
    if event.data.email is not None and "@facebook.com" in event.data.email:
        return identity_fn.BeforeSignInResponse(email_verified=True)

مسدود کردن ورود به سیستم از نشانی‌های IP خاص

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

Node.js

export const beforesignedin = beforeUserSignedIn((event) => {
  if (isSuspiciousIpAddress(event.ipAddress)) {
    throw new HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});

پایتون

@identity_fn.before_user_signed_in()
def ipban(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeSignInResponse | None:
    if is_suspicious(event.ip_address):
        raise https_fn.HttpsError(
            code=https_fn.FunctionsErrorCode.PERMISSION_DENIED, message="IP banned."
        )

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

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

Node.js

export const beforecreated = beforeUserCreated((event) => {
    if (event.credential &&
        event.credential.claims &&
        event.credential.providerId === "saml.my-provider-id") {
        return {
            // Employee ID does not change so save in persistent claims (stored in
            // Auth DB).
            customClaims: {
                eid: event.credential.claims.employeeid,
            },
        };
    }
});

export const beforesignin = beforeUserSignedIn((event) => {
    if (event.credential &&
        event.credential.claims &&
        event.credential.providerId === "saml.my-provider-id") {
        return {
            // Copy role and groups to token claims. These will not be persisted.
            sessionClaims: {
                role: event.credential.claims.role,
                groups: event.credential.claims.groups,
            },
        };
    }
});

پایتون

@identity_fn.before_user_created()
def setemployeeid(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeCreateResponse | None:
    if (
        event.credential is not None
        and event.credential.claims is not None
        and event.credential.provider_id == "saml.my-provider-id"
    ):
        return identity_fn.BeforeCreateResponse(
            custom_claims={"eid": event.credential.claims["employeeid"]}
        )


@identity_fn.before_user_signed_in()
def copyclaimstosession(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeSignInResponse | None:
    if (
        event.credential is not None
        and event.credential.claims is not None
        and event.credential.provider_id == "saml.my-provider-id"
    ):
        return identity_fn.BeforeSignInResponse(
            session_claims={
                "role": event.credential.claims["role"],
                "groups": event.credential.claims["groups"],
            }
        )

ردیابی نشانی‌های IP برای نظارت بر فعالیت‌های مشکوک

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

  1. از ادعاهای جلسه برای پیگیری نشانی IP که کاربر با آن به سیستم وارد می‌شود استفاده کنید:

    Node.js

    export const beforesignedin = beforeUserSignedIn((event) => {
      return {
        sessionClaims: {
          signInIpAddress: event.ipAddress,
        },
      };
    });
    

    پایتون

    @identity_fn.before_user_signed_in()
    def logip(
        event: identity_fn.AuthBlockingEvent,
    ) -> identity_fn.BeforeSignInResponse | None:
        return identity_fn.BeforeSignInResponse(session_claims={"signInIpAddress": event.ip_address})
    
  2. وقتی کاربری تلاش می‌کند به منابعی دسترسی پیدا کند که به اصالت‌سنجی با Firebase Authentication نیاز دارند، نشانی IP موجود در درخواست را با نشانی IP استفاده‌شده برای ورود به سیستم مقایسه کنید:

    Node.js

    app.post('/getRestrictedData', (req, res) => {
      // Get the ID token passed.
      const idToken = req.body.idToken;
      // Verify the ID token, check if revoked and decode its payload.
      admin.auth().verifyIdToken(idToken, true).then((claims) => {
        // Get request IP address
        const requestIpAddress = req.connection.remoteAddress;
        // Get sign-in IP address.
        const signInIpAddress = claims.signInIpAddress;
        // Check if the request IP address origin is suspicious relative to
        // the session IP addresses. The current request timestamp and the
        // auth_time of the ID token can provide additional signals of abuse,
        // especially if the IP address suddenly changed. If there was a sudden
        // geographical change in a short period of time, then it will give
        // stronger signals of possible abuse.
        if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) {
          // Suspicious IP address change. Require re-authentication.
          // You can also revoke all user sessions by calling:
          // admin.auth().revokeRefreshTokens(claims.sub).
          res.status(401).send({error: 'Unauthorized access. Please login again!'});
        } else {
          // Access is valid. Try to return data.
          getData(claims).then(data => {
            res.end(JSON.stringify(data);
          }, error => {
            res.status(500).send({ error: 'Server error!' })
          });
        }
      });
    });
    

    پایتون

    from firebase_admin import auth, initialize_app
    import flask
    
    initialize_app()
    flask_app = flask.Flask(__name__)
    
    @flask_app.post()
    def get_restricted_data(req: flask.Request):
        # Get the ID token passed.
        id_token = req.json().get("idToken")
    
        # Verify the ID token, check if revoked, and decode its payload.
        try:
            claims = auth.verify_id_token(id_token, check_revoked=True)
        except:
            return flask.Response(status=500)
    
        # Get request IP address.
        request_ip = req.remote_addr
    
        # Get sign-in IP address.
        signin_ip = claims["signInIpAddress"]
    
        # Check if the request IP address origin is suspicious relative to
        # the session IP addresses. The current request timestamp and the
        # auth_time of the ID token can provide additional signals of abuse,
        # especially if the IP address suddenly changed. If there was a sudden
        # geographical change in a short period of time, then it will give
        # stronger signals of possible abuse.
        if is_suspicious_change(signin_ip, request_ip):
            # Suspicious IP address change. Require re-authentication.
            # You can also revoke all user sessions by calling:
            #   auth.revoke_refresh_tokens(claims["sub"])
            return flask.Response(status=401,
                                  response="Unauthorized access. Sign in again!")
        else:
            # Access is valid. Try to return data.
            return data_from_claims(claims)
    

غربال کردن عکس‌های کاربر

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

Node.js

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (user.photoURL) {
    return isPhotoAppropriate(user.photoURL)
      .then((status) => {
        if (!status) {
          // Sanitize inappropriate photos by replacing them with guest photos.
          // Users could also be blocked from sign-up, disabled, etc.
          return {
            photoURL: PLACEHOLDER_GUEST_PHOTO_URL,
          };
        }
      });
});

پایتون

@identity_fn.before_user_created()
def sanitizeprofilephoto(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeCreateResponse | None:
    if event.data.photo_url is not None:
        score = analyze_photo_with_ml(event.data.photo_url)
        if score > THRESHOLD:
            return identity_fn.BeforeCreateResponse(photo_url=PLACEHOLDER_URL)

برای کسب اطلاعات بیشتر درباره نحوه شناسایی و پاک‌سازی تصاویر، به اسناد Cloud Vision مراجعه کنید.

دسترسی به اعتبارنامه‌های OAuth ارائه‌دهنده هویت کاربر

مثال زیر نشان می‌دهد چگونه برای کاربری که با Google به سیستم وارد شده است یک کد نوسازی دریافت کنید و از آن برای فراخوانی Google Calendar APIs استفاده کنید. رمز بازآوری برای دسترسی آفلاین ذخیره می‌شود.

Node.js

const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
  keys.web.client_id,
  keys.web.client_secret
);

export const beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  if (event.credential &&
      event.credential.providerId === 'google.com') {
    // Store the refresh token for later offline use.
    // These will only be returned if refresh tokens credentials are included
    // (enabled by Cloud console).
    return saveUserRefreshToken(
        user.uid,
        event.credential.refreshToken,
        'google.com'
      )
      .then(() => {
        // Blocking the function is not required. The function can resolve while
        // this operation continues to run in the background.
        return new Promise((resolve, reject) => {
          // For this operation to succeed, the appropriate OAuth scope should be requested
          // on sign in with Google, client-side. In this case:
          // https://www.googleapis.com/auth/calendar
          // You can check granted_scopes from within:
          // event.additionalUserInfo.profile.granted_scopes (space joined list of scopes).

          // Set access token/refresh token.
          oAuth2Client.setCredentials({
            access_token: event.credential.accessToken,
            refresh_token: event.credential.refreshToken,
          });
          const calendar = google.calendar('v3');
          // Setup Onboarding event on user's calendar.
          const event = {/** ... */};
          calendar.events.insert({
            auth: oauth2client,
            calendarId: 'primary',
            resource: event,
          }, (err, event) => {
            // Do not fail. This is a best effort approach.
            resolve();
          });
      });
    })
  }
});

پایتون

@identity_fn.before_user_created()
def savegoogletoken(
    event: identity_fn.AuthBlockingEvent,
) -> identity_fn.BeforeCreateResponse | None:
    """During sign-up, save the Google OAuth2 access token and queue up a task
    to schedule an onboarding session on the user's Google Calendar.

    You will only get an access token if you enabled it in your project's blocking
    functions settings in the Firebase console:

    https://console.firebase.google.com/project/_/authentication/settings
    """
    if event.credential is not None and event.credential.provider_id == "google.com":
        print(f"Signed in with {event.credential.provider_id}. Saving access token.")

        firestore_client: google.cloud.firestore.Client = firestore.client()
        doc_ref = firestore_client.collection("user_info").document(event.data.uid)
        doc_ref.set({"calendar_access_token": event.credential.access_token}, merge=True)

        tasks_client = google.cloud.tasks_v2.CloudTasksClient()
        task_queue = tasks_client.queue_path(
            params.PROJECT_ID.value, options.SupportedRegion.US_CENTRAL1.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)

لغو کردن حکم reCAPTCHA Enterprise برای عملیات کاربر

مثال زیر نشان می‌دهد چگونه رأی reCAPTCHA Enterprise را برای گردش‌های کاربری پشتیبانی‌شده ملغی کنید.

برای کسب اطلاعات بیشتر درباره یکپارچه‌سازی reCAPTCHA Enterprise با «اصالت‌سنجی Firebase»، به فعال کردن reCAPTCHA Enterprise مراجعه کنید.

از توابع مسدودکننده می‌توان برای مجاز یا مسدود کردن جریان‌ها براساس عوامل سفارشی استفاده کرد، و ازاین‌طریق نتیجه ارائه‌شده توسط reCAPTCHA Enterprise را ملغی کرد.

Node.js

const { beforeSmsSent } = require("firebase-functions/v2/identity");
exports.beforesmssentv2 = beforeSmsSent((event) => {
 if (
   event.smsType === "SIGN_IN_OR_SIGN_UP" &&
   event.additionalUserInfo.phoneNumber.includes('+91')
 ) {
   return {
     recaptchaActionOverride: "ALLOW",
   };
 }

 // Allow users to sign in with recaptcha score greater than 0.5
 if (event.additionalUserInfo.recaptchaScore > 0.5) {
   return {
     recaptchaActionOverride: 'ALLOW',
   };
 }

 // Block all others.
 return  {
   recaptchaActionOverride: 'BLOCK',
 }
});