توسيع نطاق مصادقة Firebase باستخدام وظائف الحظر


تتيح لك وظائف الحظر تنفيذ رمز مخصّص يُعدّل نتيجة تسجيل أحد المستخدمين أو تسجيل الدخول إلى تطبيقك. على سبيل المثال، يمكنك منع أحد المستخدمين من المصادقة إذا لم يستوفِ معايير معيّنة، أو تعديل معلومات أحد المستخدمين قبل إعادتها إلى تطبيق العميل.

قبل البدء

لاستخدام وظائف الحظر، يجب ترقية مشروعك على Firebase إلى الإصدار Firebase Authentication with Identity Platform. إذا لم يسبق لك الترقية، عليك إجراء ذلك أولاً.

فهم دوال الحظر

يمكنك تسجيل وظائف الحظر لهذه الأحداث:

  • قبل إنشاء حساب المستخدِم: يتم تفعيله قبل حفظ مستخدِم جديد في قاعدة بيانات Firebase Authentication وقبل عرض رمز مميّز إلى تطبيق العميل.

  • قبل تسجيل دخول المستخدم: يتم تفعيلها بعد إثبات صحة بيانات اعتماد المستخدم، ولكن قبل أن يعرض Firebase Authentication رمز تعريف إلى تطبيق العميل. إذا كان تطبيقك يستخدم المصادقة المتعدّدة العوامل، يتم تفعيل الدالة بعد أن يُثبت المستخدم صحة العامل الثاني. يُرجى العلم أنّ إنشاء ملف شخصي جديد للمستخدم يؤدي أيضًا إلى بدء هذين الحدثَين.

  • قبل إرسال رسالة إلكترونية (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 واجهة سطر الأوامر:

    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 أو عنوان البريد الإلكتروني أو كلمة المرور. 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 في كلتا معالجتَي الأحداث، ستحلّ القيمة المضبوطة في 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.
  • رمز مميّز لإعادة التحميل: يتم توفيره في حال اختيار نطاق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، سيتم تمرير بيانات الاعتماد التالية:

  • رمز الدخول: يتم عرض رمز دخول يمكن استبداله برمز دخول آخر. تعرَّف على المزيد من المعلومات حول الأنواع المختلفة من رموز الدخول المختلفة المتوافقة مع 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 من التسجيل باستخدام تطبيقك:

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". يتم تخزين رمز إعادة التنشيط للوصول إلى البيانات بلا إنترنت.

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 للاطّلاع على مزيد من المعلومات عن دمج reCAPTCHA Enterprise مع Firebase Authentication.

يمكن استخدام دوال الحظر للسماح بالعمليات أو حظرها استنادًا إلى عوامل مخصّصة، وبالتالي إلغاء النتيجة التي تقدّمها 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',
 }
});