إضافة ميزة تسجيل الدخول إلى تطبيق iOS باستخدام FirebaseUI

FirebaseUI لـ SwiftUI هي مكتبة حديثة مصمّمة خصيصًا لـ SwiftUI ومستندة إلى Firebase Authentication، وتوفّر عمليات تسجيل دخول مُعدّة مسبقًا لتطبيقك.

تتضمّن FirebaseUI for SwiftUI المزايا التالية:

  • واجهة مستخدم تلقائية ذات آراء مسبقة: أضِف عملية تسجيل دخول كاملة باستخدام AuthPickerView.
  • قابلة للتخصيص: يمكنك عرض الأزرار التلقائية في تصميمك الخاص أو إنشاء تجربة مخصّصة بالكامل.
  • ربط الحسابات المجهولة: يمكنك ترقية المستخدمين المجهولين بدلاً من استبدالهم.
  • إدارة الحساب: مسارات مدمجة لعمليات الاشتراك واسترداد كلمة المرور وإدارة الحساب
  • مزوّدو خدمات متعدّدون: البريد الإلكتروني/كلمة المرور، ورابط البريد الإلكتروني، والمصادقة عبر الهاتف، وApple وGoogle وFacebook وTwitter ومزوّدو خدمات OAuth2/OIDC العاديون
  • ميزات المصادقة الحديثة: إتاحة ميزة المصادقة المتعدّدة العوامل (MFA) وواجهات برمجة التطبيقات غير المتزامنة/المتزامنة.

قبل البدء

تثبيت

يتم توفير FirebaseUI لـ SwiftUI كحزمة Swift. لتثبيت الحزمة في مشروع Xcode، اتّبِع الخطوات التالية:

  1. في Xcode، انقر على ملف > إضافة موارد الاعتمادية للحزمة (File > Add Package Dependencies).

  2. أدخِل عنوان URL للحزمة:

    https://github.com/firebase/FirebaseUI-iOS
    
  3. في قائمة قاعدة التبعية، اختَر حتى الإصدار الرئيسي التالي واضبط الحد الأدنى للإصدار على أحدث إصدار.

  4. انقر على إضافة حزمة، ثم اختَر المكتبات التي تريد إضافتها إلى مشروعك.

    المكتبة التالية مطلوبة دائمًا. ويتضمّن التبعيات الأساسية، بالإضافة إلى إمكانية تسجيل الدخول باستخدام عنوان البريد الإلكتروني وكلمة المرور وتسجيل الدخول باستخدام رابط البريد الإلكتروني.

    • FirebaseAuthSwiftUI

     إذا أردت توفير طرق أخرى لتسجيل الدخول، اختَر أيضًا مكتبة واحدة أو أكثر من المكتبات التالية:

    • ‫FirebaseAppleSwiftUI (تسجيل الدخول باستخدام حساب على Apple)
    • FirebaseGoogleSwiftUI (تسجيل الدخول باستخدام حساب Google)
    • ‫FirebaseFacebookSwiftUI (تسجيل الدخول باستخدام Facebook)
    • ‫FirebasePhoneAuthSwiftUI (المصادقة باستخدام الهاتف)
    • ‫FirebaseTwitterSwiftUI (تسجيل الدخول باستخدام Twitter)
    • ‫FirebaseOAuthSwiftUI (مقدّمو خدمات OAuth وOIDC العاديون، مثل GitHub وMicrosoft وYahoo)
  5. انقر على إضافة حزمة لتثبيت المكتبات المحدّدة.

  6. اضبط AuthService في FirebaseUIView في أداة التهيئة الخاصة بالعرض ذي المستوى الأعلى، ومرِّر الخدمة إلى العروض الفرعية باستخدام البيئة.

    import FirebaseAuthSwiftUI
    import SwiftUI
    
    struct ContentView: View {
      let authService: AuthService
    
      init() {
        let configuration = AuthConfiguration()
    
        authService = AuthService(configuration: configuration)
          .withEmailSignIn() // Or whatever sign-in methods you want to support.
                             // See the next section.
      }
    
      var body: some View {
        AuthPickerView { // AuthPickerView (the prebuilt View) or a custom View.
            Text("Welcome to your app!")
        }
        .environment(authService)
      }
    }
    

إعداد طُرق تسجيل الدخول

تتطلّب كل طريقة من طرق تسجيل الدخول المتوافقة بعض خطوات الإعداد الإضافية. وسِّع الأقسام أدناه واتّبِع التعليمات لإعداد مقدّمي الخدمات الفرديين.

عنوان البريد الإلكتروني وكلمة المرور

  1. من قسم الأمان > المصادقة > طريقة تسجيل الدخول في وحدة تحكّم Firebase، فعِّل موفّر البريد الإلكتروني/كلمة المرور.

  2. سجِّل موفِّر الهوية في مثيل AuthService:

    let authService = AuthService()
      .withEmailSignIn()
    

لاستخدام ميزة تسجيل الدخول من خلال رابط البريد الإلكتروني بدون كلمة مرور، اتّبِع الخطوات التالية:

  1. من قسم الأمان > المصادقة > طريقة تسجيل الدخول في وحدة تحكّم Firebase، فعِّل البريد الإلكتروني/كلمة المرور، ثم فعِّل تسجيل الدخول باستخدام رابط البريد الإلكتروني. يُرجى العِلم أنّه يجب تفعيل تسجيل الدخول باستخدام البريد الإلكتروني أو كلمة المرور لاستخدام تسجيل الدخول باستخدام رابط البريد الإلكتروني.

  2. أضِف نطاق الرابط إلى النطاقات المعتمَدة.

  3. اضبط ActionCodeSettings، ومرِّره إلى AuthConfiguration، وسجِّل موفّر الخدمة:

    let actionCodeSettings = ActionCodeSettings()
    actionCodeSettings.handleCodeInApp = true
    actionCodeSettings.url = URL(string: "https://yourapp.firebaseapp.com")
    
    guard let bundleID = Bundle.main.bundleIdentifier else {
      fatalError("Missing bundle identifier for email link authentication setup.")
    }
    actionCodeSettings.setIOSBundleID(bundleID)
    
    let configuration = AuthConfiguration(
      emailLinkSignInActionCodeSettings: actionCodeSettings
    )
    
    let authService = AuthService(configuration: configuration)
      .withEmailLinkSignIn()
    
  4. في AppDelegate نفسه الذي تستدعي فيه FirebaseApp.configure()، أضِف منطقًا إلى الطريقة application(_:open:options:) التي تعرض true عندما يكون عنوان URL الذي تم فتحه هو رابط تسجيل دخول Firebase Authentication. تشير هذه المنطقية إلى أنّ FirebaseUI قد تعاملت مع الرابط، وبالتالي لا ينبغي أن تتم معالجته من خلال معالجات الروابط الأخرى.

    import FacebookCore
    import FirebaseAuth
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        FirebaseApp.configure()
        return true
      }
    
      func application(
        _ application: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey: Any] = [:]
      ) -> Bool {
        if Auth.auth().canHandle(url) {
          return true
        }
        return false
      }
    }
    
  5. إذا أنشأت طرق عرض مخصّصة، استخدِم authService.handleSignInLink(url:) عندما يفتح الرابط تطبيقك.

Apple

لاستخدام ميزة "تسجيل الدخول باستخدام حساب على Apple"، اتّبِع الخطوات التالية:

  1. اضبط إعدادات ميزة "تسجيل الدخول باستخدام حساب على Apple" باتّباع الخطوات التالية:

    1. فعِّل ميزة "تسجيل الدخول باستخدام Apple" لتطبيقك على صفحة الشهادات والمعرّفات وملفات التعريف في موقع المطوّرين الإلكتروني الخاص بشركة Apple.
    2. اربط موقعك الإلكتروني بتطبيقك كما هو موضّح في القسم الأول من مقالة إعداد ميزة "تسجيل الدخول باستخدام Apple" على الويب. عندما يُطلب منك ذلك، سجِّل عنوان URL التالي كعنوان URL للرجوع:
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      يمكنك الحصول على رقم تعريف مشروع Firebase الخاص بك من صفحة إعدادات وحدة التحكّم Firebase. عند الانتهاء، سجِّل رقم تعريف الخدمة الجديد الذي ستحتاج إليه في القسم التالي.
    3. إنشاء مفتاح خاص لخدمة "تسجيل الدخول باستخدام Apple" ستحتاج إلى مفتاحك الخاص الجديد ورقم تعريف المفتاح في القسم التالي.
    4. إذا كنت تستخدم أيًا من ميزات Firebase Authentication التي ترسل رسائل إلكترونية إلى المستخدمين، بما في ذلك تسجيل الدخول باستخدام رابط عبر البريد الإلكتروني، وإثبات ملكية عنوان البريد الإلكتروني، وإلغاء تغيير الحساب، وغيرها، عليك ضبط خدمة إعادة توجيه البريد الإلكتروني الخاص من Apple وتسجيل noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (أو نطاق نموذج البريد الإلكتروني المخصّص) حتى تتمكّن Apple من إعادة توجيه الرسائل الإلكترونية التي يرسلها Firebase Authentication إلى عناوين بريد إلكتروني مجهولة الهوية من Apple.
  2. من قسم الأمان > المصادقة > طريقة تسجيل الدخول في وحدة تحكّم Firebase، فعِّل Apple.

    • حدِّد معرّف الخدمة الذي أنشأته في القسم السابق.
    • في قسم إعدادات مسار رمز OAuth، حدِّد رقم تعريف فريق Apple والمفتاح الخاص ورقم تعريف المفتاح اللذين أنشأتهما في القسم السابق.
  3. في Xcode، افتح قسم التوقيع والقدرات في أداة تعديل المشروع وأضِف إمكانية تسجيل الدخول باستخدام Apple.

  4. سجِّل موفِّر الهوية في مثيل AuthService:

    let authService = AuthService()
      .withAppleSignIn()
    

Google

لاستخدام ميزة "تسجيل الدخول باستخدام حساب Google"، اتّبِع الخطوات التالية:

  1. من قسم الأمان > المصادقة > طريقة تسجيل الدخول في لوحة تحكّم Firebase، فعِّل موفّر Google.

  2. نزِّل نسخة جديدة من ملف GoogleService-Info.plist الخاص بمشروعك وانسخها إلى مشروع Xcode. استبدِل أي إصدارات حالية بالإصدار الجديد.

  3. أضِف مخططات عناوين URL المخصّصة إلى مشروع Xcode باتّباع الخطوات التالية:

    1. افتح إعدادات مشروعك: انقر على اسم المشروع في عرض الشجرة على اليمين. اختَر تطبيقك من قسم الاستهدافات، ثم انقر على علامة التبويب المعلومات، ووسِّع قسم أنواع عناوين URL.

    2. انقر على الزر +، وأضِف مخطط URL لمعرّف العميل المعكوس. للعثور على هذه القيمة، افتح ملف الإعداد GoogleService-Info.plist وابحث عن المفتاح REVERSED_CLIENT_ID. انسخ قيمة هذا المفتاح، وألصِقها في مربّع مخططات عناوين URL في صفحة الإعدادات. اترك الحقول الأخرى بدون تغيير.

      عند الانتهاء، من المفترض أن يبدو الإعداد مشابهًا لما يلي (ولكن مع القيم الخاصة بتطبيقك):

  4. سجِّل موفِّر الهوية في مثيل AuthService:

    let authService = AuthService()
      .withGoogleSignIn()
    

Facebook

لاستخدام ميزة "تسجيل الدخول عبر Facebook"، اتّبِع الخطوات التالية:

  1. يمكنك إعداد حزمة تطوير البرامج (SDK) الخاصة بميزة "تسجيل الدخول باستخدام Facebook" على أجهزة iOS من خلال اتّباع التعليمات الواردة على موقع Meta للمطوّرين. تخطَّ الخطوة الأخيرة، "إضافة ميزة تسجيل الدخول باستخدام Facebook إلى الرمز".

  2. من قسم الأمان > المصادقة > طريقة تسجيل الدخول في وحدة تحكّم Firebase، فعِّل موفّر Facebook. ستحتاج إلى رقم تعريف تطبيق Facebook ورمز التطبيق السري من موقع Meta for Developers الإلكتروني.

  3. سجِّل موفِّر الهوية في مثيل AuthService:

    let authService = AuthService()
     .withFacebookSignIn()
    

رقم الهاتف

لاستخدام مصادقة الهاتف، اتّبِع الخطوات التالية:

  1. من قسم الأمان > المصادقة > طريقة تسجيل الدخول في Firebase Console، فعِّل موفّر الهاتف.

  2. اضبط إعدادات APNs لتطبيقك وفقًا للتعليمات الواردة في قسم بدء تلقّي الإشعارات الصامتة.

  3. أضِف مخططات عناوين URL المخصّصة إلى مشروع Xcode باتّباع الخطوات التالية:

    1. افتح إعدادات مشروعك: انقر على اسم المشروع في عرض الشجرة على اليمين. اختَر تطبيقك من قسم الاستهدافات، ثم انقر على علامة التبويب المعلومات، ووسِّع قسم أنواع عناوين URL.

    2. انقر على الزر + وأضِف معرّف التطبيق المرمّز كمخطّط URL. للعثور على هذه القيمة، افتح الإعدادات > الإعدادات العامة في وحدة تحكّم Firebase.

      عند الانتهاء، من المفترض أن يبدو الإعداد مشابهًا لما يلي (ولكن مع القيم الخاصة بتطبيقك):

  4. في AppDelegate نفسه الذي تستدعي فيه FirebaseApp.configure()، أضِف معالجات الرموز المميزة لخدمة APNs:

    import FacebookCore
    import FirebaseAuth
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        FirebaseApp.configure()
        return true
      }
    
      func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
      ) {
        #if DEBUG
        Auth.auth().setAPNSToken(deviceToken, type: .sandbox)
        #else
        Auth.auth().setAPNSToken(deviceToken, type: .prod)
        #endif
      }
    }
    
  5. سجِّل موفِّر الهوية في مثيل AuthService:

    let authService = AuthService()
      .withPhoneSignIn()
    

‫Twitter (X)

لاستخدام ميزة "تسجيل الدخول عبر Twitter"، اتّبِع الخطوات التالية:

  1. يمكنك إنشاء بيانات اعتماد X API باتّباع التعليمات على موقع X Developers الإلكتروني.

  2. من قسم الأمان > المصادقة > طريقة تسجيل الدخول في وحدة تحكّم Firebase، فعِّل موفّر Twitter. ستحتاج إلى مفتاح واجهة برمجة التطبيقات ورمزها السري من X Developer Console.

  3. سجِّل موفِّر الهوية في مثيل AuthService:

    let authService = AuthService()
      .withTwitterSignIn()
    

موفّرو OAuth2 وOIDC العاديون

تتوافق FirebaseUI أيضًا مع موفّري OAuth المضمّنين، مثل GitHub وMicrosoft وYahoo، بالإضافة إلى موفّري OIDC المخصّصين الذين تم إعدادهم في Firebase Authentication.

 let authService = AuthService()
  .withOAuthSignIn(OAuthProviderSwift.github())
  .withOAuthSignIn(OAuthProviderSwift.microsoft())
  .withOAuthSignIn(OAuthProviderSwift.yahoo())

بالنسبة إلى موفّري OIDC المخصّصين، عليك ضبط إعدادات الموفّر أولاً في خدمة Firebase Authentication، ثم إنشاء OAuthProviderSwift باستخدام معرّف الموفّر وإعدادات الزر:

let lineProvider = OAuthProviderSwift(
  providerId: "oidc.line",
  buttonLabel: "Sign in with LINE",
  displayName: "LINE",
  iconSystemName: "person.crop.circle.badge.checkmark",
  buttonBackgroundColor: .green,
  buttonForegroundColor: .white
)

let authService = AuthService()
  .withOAuthSignIn(lineProvider)

استخدام طريقة عرض المصادقة المُنشأة مسبقًا

توفّر FirebaseUI لـ SwiftUI AuthPickerViewواجهة مستخدم للمصادقة مسبقة الإنشاء ومصمّمة بشكل محدّد ، وهي تتولّى عملية المصادقة بأكملها نيابةً عنك. وهذه هي أسهل طريقة لإضافة مصادقة إلى تطبيقك.

مثال

في ما يلي مثال على استخدام AuthPickerView مع عدة موفّري خدمات وخيارات إعداد:

import FirebaseAppleSwiftUI
import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import SwiftUI

struct ContentView: View {
  let authService: AuthService

  init() {
    // Create configuration with options
    let configuration = AuthConfiguration(
      tosUrl: URL(string: "https://example.com/tos"),
      privacyPolicyUrl: URL(string: "https://example.com/privacy"),
      shouldAutoUpgradeAnonymousUsers: true
    )

    // Initialize AuthService with multiple providers
    authService = AuthService(configuration: configuration)
      .withEmailSignIn()
      .withAppleSignIn()
      .withGoogleSignIn()
  }

  var body: some View {
    AuthPickerView {
      authenticatedContent
    }
    .environment(authService)
  }

  var authenticatedContent: some View {
    NavigationStack {
      VStack(spacing: 20) {
        if authService.authenticationState == .authenticated {
          Text("Authenticated")
          
          Button("Manage Account") {
            authService.isPresented = true
          }
          .buttonStyle(.bordered)
          
          Button("Sign Out") {
            Task {
              try? await authService.signOut()
            }
          }
          .buttonStyle(.borderedProminent)
        } else {
          Text("Not Authenticated")
          
          Button("Sign In") {
            authService.isPresented = true
          }
          .buttonStyle(.borderedProminent)
        }
      }
      .navigationTitle("My App")
    }
    .onChange(of: authService.authenticationState) { _, newValue in
      // Automatically show auth UI when not authenticated
      if newValue != .authenticating {
        authService.isPresented = (newValue == .unauthenticated)
      }
    }
  }
}

التخصيص المعتاد

على الرغم من أنّ جميع مَعلمات AuthConfiguration اختيارية، إلا أنّ معظم التطبيقات تخصّص الإعدادات التالية كحد أدنى:

let configuration = AuthConfiguration(
    logo: ImageResource.exampleLogoAsset,
    customStringsBundle: .main,
    tosUrl: URL(string: "https://example.com/tos"),
    privacyPolicyUrl: URL(string: "https://example.com/privacy"),
)
  • ‫logo: صورة الشعار المعروضة في ورقة المصادقة للحصول على معلومات حول تضمين مادة عرض خاصة بك على شكل صورة، راجِع إضافة صور إلى مشروع Xcode.

  • ‫customStringsBundle: استخدِم السلاسل المخصّصة من الحزمة المحدّدة. يتم استخدام هذا الحقل في عملية الترجمة إلى لغات أخرى، ولكن أيضًا لتخصيص السلاسل التلقائية التي يستخدمها AuthPickerView. على سبيل المثال، لضبط الرسالة المعروضة في أعلى ورقة المصادقة، أنشئ Localizable.strings مع المحتوى التالي:

    "Sign in with Firebase" = "Sign in to use ExampleApp";
    

المحتوى المضمَّن في العرض المُعدّ مسبقًا

عند استخدام AuthPickerView، ستحصل على ما يلي:

  1. عرض جدول البيانات: تظهر واجهة مستخدم المصادقة كصفحة مشروطة
  2. التنقّل المضمّن: التنقّل التلقائي بين شاشات تسجيل الدخول واسترداد كلمة المرور والمصادقة المتعددة العوامل ورابط البريد الإلكتروني والتحقّق من رقم الهاتف
  3. إدارة حالة المصادقة: يتم التبديل تلقائيًا بين واجهة مستخدم المصادقة والمحتوى استنادًا إلى authService.authenticationState
  4. التحكّم من خلال isPresented: يمكنك التحكّم في وقت ظهور ورقة المصادقة من خلال ضبط authService.isPresented = true/false

السلوكيات المتحيزة

تتضمّن AuthPickerView الإعدادات التلقائية التي تحدّد كيفية التعامل مع عدة سيناريوهات معقّدة:

1. حلّ تعارضات الحسابات

عند حدوث تعارض في الحساب (على سبيل المثال، تسجيل الدخول باستخدام بيانات اعتماد مرتبطة بحساب آخر)، تتولّى AuthPickerView معالجة ذلك تلقائيًا:

  • تعارضات الترقية بدون تحديد الهوية: في حال تفعيل shouldAutoUpgradeAnonymousUsers وحدث تعارض أثناء الترقية بدون تحديد الهوية، يسجّل النظام تلقائيًا خروج المستخدم بدون تحديد الهوية ويسجّل الدخول باستخدام بيانات الاعتماد الجديدة.
  • تعارضات أخرى: في حال حدوث تعارض بين بيانات الاعتماد في حسابات غير مجهولة الهوية، يخزّن النظام بيانات الاعتماد المعلقة ويحاول ربطها بعد تسجيل الدخول بنجاح.

تتم معالجة ذلك من خلال AccountConflictModifier المطبَّق على مستوى NavigationStack.

2. المصادقة المتعدّدة العوامل (MFA)

عند تفعيل المصادقة المتعددة العوامل في إعداداتك:

  • رصد تلقائي عند الحاجة إلى المصادقة المتعددة العوامل أثناء تسجيل الدخول
  • عرض شاشات حلّ المصادقة المتعدّدة العوامل المناسبة (الرسائل القصيرة أو كلمة المرور المؤقتة لمرة واحدة)
  • يتعامل مع عمليات تسجيل وإدارة المصادقة المتعددة العوامل
  • تتيح استخدام عوامل المصادقة المستندة إلى الرسائل القصيرة وكلمة المرور الصالحة لمرة واحدة المستندة إلى الوقت (TOTP)
3- معالجة الأخطاء

تتضمّن طرق العرض التلقائية معالجة مدمجة للأخطاء:

  • عرض رسائل خطأ سهلة الاستخدام في مربّعات حوار التنبيه
  • تستبعد تلقائيًا الأخطاء التي يتم التعامل معها داخليًا (مثل أخطاء الإلغاء والتعارضات التي يتم التعامل معها تلقائيًا)
  • استخدام رسائل الخطأ المترجَمة من خلال StringUtils
  • يتم نشر الأخطاء من خلال مفتاح بيئة reportError

عند إعداد ميزة تسجيل الدخول باستخدام رابط يتم إرساله إلى عنوان البريد الإلكتروني:

  • تخزين عنوان البريد الإلكتروني تلقائيًا في مساحة تخزين التطبيق
  • يتعامل مع التنقّل عبر الروابط لصفحات معيّنة من الرسائل الإلكترونية
  • إدارة عملية تأكيد عنوان البريد الإلكتروني بالكامل
  • إتاحة ترقية المستخدمين مجهولي الهوية من خلال رابط البريد الإلكتروني
5- الترقية التلقائية للمستخدمين مجهولي الهوية

عندما يكون الخيار "shouldAutoUpgradeAnonymousUsers" مفعّلاً:

  • محاولة ربط الحسابات المجهولة تلقائيًا ببيانات اعتماد تسجيل الدخول الجديدة
  • الاحتفاظ ببيانات المستخدم من خلال الترقية بدلاً من استبدال الجلسات المجهولة
  • التعامل مع تعارضات الترقية بسلاسة
6. إعادة المصادقة في طرق العرض التلقائية

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

عندما تتطلّب عملية حسّاسة إعادة المصادقة، يتم تلقائيًا تنفيذ ما يلي في طرق العرض التلقائية:

  • مقدّمو خدمة OAuth (مثل Google وApple وFacebook وTwitter وما إلى ذلك): اعرض تنبيهًا يطلب من المستخدم تأكيد العملية، ثم احصل تلقائيًا على بيانات اعتماد جديدة وأكمِل العملية.

  • البريد الإلكتروني/كلمة المرور: اعرض ورقة تطلب من المستخدم إدخال كلمة المرور قبل المتابعة.

  • رابط إلكتروني: عرض تنبيه يطلب إرسال رسالة إلكترونية لتأكيد الحساب، ثم عرض ورقة تتضمّن تعليمات للتحقّق من الرسالة الإلكترونية ينقر المستخدم على الرابط في رسالته الإلكترونية لإكمال عملية إعادة المصادقة.

  • الهاتف: عرض تنبيه يوضّح أنّه يجب إثبات ملكية الهاتف، ثم عرض ورقة لإثبات ملكية الهاتف باستخدام رمز SMS

تتم إعادة محاولة العملية تلقائيًا بعد إعادة المصادقة بنجاح. لا يلزم توفير أي رمز إضافي عند استخدام AuthPickerView أو طرق عرض إدارة الحسابات المضمّنة (UpdatePasswordView وSignedInView وما إلى ذلك).

إعدادات متقدّمة: إنشاء طرق عرض مصادقة مخصّصة

إذا كنت بحاجة إلى المزيد من التحكّم في واجهة المستخدم أو مسار التنقّل، يمكنك إنشاء طرق عرض مصادقة مخصّصة مع الاستفادة من AuthService لمنطق المصادقة.

يمكنك دمج عناصر FirebaseUI مع منطق مخصّص بطرق عديدة. تحتوي الأقسام التالية على أمثلة لبعض طرق التخصيص التي يمكنك استخدامها.

الطريقة 1: أزرار مخصّصة مع registerProvider()

للحصول على تحكّم كامل في مظهر الزر، يمكنك إنشاء عملية تنفيذ AuthProviderUI مخصّصة خاصة بك تتضمّن أي موفّر وتعرض طريقة عرض الزر المخصّص.

إنشاء واجهة مستخدم مخصّصة لمقدّم الخدمة

في ما يلي كيفية إنشاء زر مخصّص على Twitter كمثال:

import FirebaseAuthSwiftUI
import FirebaseTwitterSwiftUI
import SwiftUI

// Step 1: Create your custom button view
struct CustomTwitterButton: View {
  let provider: TwitterProviderSwift
  @Environment(AuthService.self) private var authService
  @Environment(\.mfaHandler) private var mfaHandler

  var body: some View {
    Button {
      Task {
        do {
          let outcome = try await authService.signIn(provider)

          // Handle MFA if required
          if case let .mfaRequired(mfaInfo) = outcome,
             let onMFA = mfaHandler {
            onMFA(mfaInfo)
          }
        } catch {
          // Do Something Else
        }
      }
    } label: {
      HStack { // Your custom icon
        Text("Sign in with Twitter")
          .fontWeight(.semibold)
      }
      .frame(maxWidth: .infinity)
      .padding()
      .background(
        LinearGradient(
          colors: [Color.blue, Color.cyan],
          startPoint: .leading,
          endPoint: .trailing
        )
      )
      .foregroundColor(.white)
      .cornerRadius(12)
      .shadow(radius: 4)
    }
  }
}

// Step 2: Create a custom AuthProviderUI wrapper
class CustomTwitterProviderAuthUI: AuthProviderUI {
  private let typedProvider: TwitterProviderSwift
  var provider: AuthProviderSwift { typedProvider }
  let id: String = "twitter.com"

  init(provider: TwitterProviderSwift = TwitterProviderSwift()) {
    typedProvider = provider
  }

  @MainActor func authButton() -> AnyView {
    AnyView(CustomTwitterButton(provider: typedProvider))
  }
}

// Step 3: Use it in your app
struct ContentView: View {
  let authService: AuthService

  init() {
    let configuration = AuthConfiguration()
    authService = AuthService(configuration: configuration)

    // Register your custom provider UI
    authService.registerProvider(
      providerWithButton: CustomTwitterProviderAuthUI()
    )
    authService.isPresented = true
  }

  var body: some View {
    AuthPickerView {
      usersApp
    }
    .environment(authService)
  }

  var usersApp: some View {
    NavigationStack {
      VStack {
        Button {
          authService.isPresented = true
        } label: {
          Text("Authenticate")
        }
      }
    }
  }
}

مثال على زر مخصّص مبسّط

يمكنك أيضًا إنشاء أزرار مخصّصة أبسط لأي مقدّم خدمة:

import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import FirebaseAppleSwiftUI
import SwiftUI

// Custom Google Provider UI
class CustomGoogleProviderAuthUI: AuthProviderUI {
  private let typedProvider: GoogleProviderSwift
  var provider: AuthProviderSwift { typedProvider }
  let id: String = "google.com"
  
  init() {
    typedProvider = GoogleProviderSwift()
  }
  
  @MainActor func authButton() -> AnyView {
    AnyView(CustomGoogleButton(provider: typedProvider))
  }
}

struct CustomGoogleButton: View {
  let provider: GoogleProviderSwift
  @Environment(AuthService.self) private var authService
  
  var body: some View {
    Button {
      Task {
        try? await authService.signIn(provider)
      }
    } label: {
      HStack {
        Image(systemName: "g.circle.fill")
        Text("My Custom Google Button")
      }
      .frame(maxWidth: .infinity)
      .padding()
      .background(Color.purple) // Your custom color
      .foregroundColor(.white)
      .cornerRadius(10)
    }
  }
}

// Then use it
struct ContentView: View {
  let authService: AuthService
  
  init() {
    let configuration = AuthConfiguration()
    authService = AuthService(configuration: configuration)
      .withAppleSignIn() // Use default Apple button
    
    // Use custom Google button
    authService.registerProvider(
      providerWithButton: CustomGoogleProviderAuthUI()
    )
  }
  
  var body: some View {
    AuthPickerView {
      Text("App Content")
    }
    .environment(authService)
  }
}

تعمل هذه الطريقة مع جميع موفّري الخدمات: Google وApple وTwitter وFacebook والهاتف وموفّرو خدمات OAuth. ما عليك سوى إنشاء طريقة عرض الزر المخصّص وتضمينها في فئة متوافقة مع AuthProviderUI.

الطريقة 2: الأزرار التلقائية مع طرق عرض مخصّصة

يمكنك استخدام AuthService.renderButtons() وتخطّي AuthPickerView لعرض أزرار المصادقة التلقائية مع توفير التنسيق والتنقّل الخاصَّين بك:

import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import FirebaseAppleSwiftUI
import SwiftUI

struct CustomAuthView: View {
  @Environment(AuthService.self) private var authService

  var body: some View {
    VStack(spacing: 30) {
      // Your custom logo/branding
      Image("app-logo")
        .resizable()
        .frame(width: 150, height: 150)
      
      Text("Welcome to My App")
        .font(.largeTitle)
        .fontWeight(.bold)
      
      Text("Sign in to continue")
        .font(.subheadline)
        .foregroundStyle(.secondary)
      
      // Render default auth buttons
      authService.renderButtons(spacing: 12)
        .padding()
    }
    .padding()
  }
}

struct ContentView: View {
  init() {
    let configuration = AuthConfiguration()
    
    authService = AuthService(configuration: configuration)
      .withGoogleSignIn()
      .withAppleSignIn()
  }
  
  let authService: AuthService

  var body: some View {
    NavigationStack {
      if authService.authenticationState == .authenticated {
        Text("Authenticated!")
      } else {
        CustomAuthView()
      }
    }
    .environment(authService)
  }
}

الطريقة 3: طرق عرض مخصّصة مع عناصر تنقّل مخصّصة

للتحكّم الكامل في العملية بأكملها، يمكنك تجاوز AuthPickerView وإنشاء نظام التنقّل الخاص بك:

import FirebaseAuth
import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import SwiftUI

enum CustomAuthRoute {
  case signIn
  case phoneVerification
  case mfaResolution
}

struct ContentView: View {
  private let authService: AuthService
  @State private var navigationPath: [CustomAuthRoute] = []
  @State private var errorMessage: String?

  init() {
    let configuration = AuthConfiguration()
    self.authService = AuthService(configuration: configuration)
      .withGoogleSignIn()
      .withPhoneSignIn()
  }

  var body: some View {
    NavigationStack(path: $navigationPath) {
      Group {
        if authService.authenticationState == .authenticated {
          authenticatedView
        } else {
          customSignInView
        }
      }
      .navigationDestination(for: CustomAuthRoute.self) { route in
        switch route {
        case .signIn:
          customSignInView
        case .phoneVerification:
          customPhoneVerificationView
        case .mfaResolution:
          customMFAView
        }
      }
    }
    .environment(authService)
    .alert("Error", isPresented: .constant(errorMessage != nil)) {
      Button("OK") {
        errorMessage = nil
      }
    } message: {
      Text(errorMessage ?? "")
    }
  }

  var customSignInView: some View {
    VStack(spacing: 20) {
      Text("Custom Sign In")
        .font(.title)
      
      Button("Sign in with Google") {
        Task {
          do {
            let provider = GoogleProviderSwift(clientID: Auth.auth().app?.options.clientID ?? "")
            let outcome = try await authService.signIn(provider)
            
            // Handle MFA if required
            if case .mfaRequired = outcome {
              navigationPath.append(.mfaResolution)
            }
          } catch {
            errorMessage = error.localizedDescription
          }
        }
      }
      .buttonStyle(.borderedProminent)
      
      Button("Phone Sign In") {
        navigationPath.append(.phoneVerification)
      }
      .buttonStyle(.bordered)
    }
    .padding()
  }

  var customPhoneVerificationView: some View {
    Text("Custom Phone Verification View")
    // Implement your custom phone auth UI here
  }

  var customMFAView: some View {
    Text("Custom MFA Resolution View")
    // Implement your custom MFA UI here
  }

  var authenticatedView: some View {
    VStack(spacing: 20) {
      Text("Welcome!")
      Text("Email: \(authService.currentUser?.email ?? "N/A")")
      
      Button("Sign Out") {
        Task {
          try? await authService.signOut()
        }
      }
      .buttonStyle(.borderedProminent)
    }
  }
}

اعتبارات مهمة بشأن طرق العرض المخصّصة

عند إنشاء طرق عرض مخصّصة، عليك التعامل مع العديد من الأمور بنفسك التي يتعامل معها AuthPickerView تلقائيًا:

  1. تعارضات الحسابات: يمكنك تنفيذ استراتيجية حلّ التعارضات الخاصة بك باستخدام AuthServiceError.accountConflict.
  2. التعامل مع المصادقة المتعدّدة العوامل: تحقَّق من SignInOutcome بحثًا عن .mfaRequired وتعامل مع حلّ المصادقة المتعدّدة العوامل يدويًا
  3. ترقية المستخدمين المجهولين: التعامل مع ربط الحسابات المجهولة في حال تفعيل shouldAutoUpgradeAnonymousUsers
  4. حالة التنقّل: إدارة التنقّل بين شاشات المصادقة المختلفة (تأكيد الحساب عبر الهاتف واسترداد كلمة المرور وما إلى ذلك)
  5. حالات التحميل: عرض مؤشرات التحميل أثناء عمليات المصادقة غير المتزامنة من خلال مراقبة authService.authenticationState
  6. إعادة المصادقة: التعامل مع أخطاء إعادة المصادقة للعمليات الحسّاسة (راجِع إعادة المصادقة في طرق العرض المخصّصة أدناه)

إعادة المصادقة في طرق العرض المخصّصة

عند إنشاء طرق عرض مخصّصة، تعامَل مع إعادة المصادقة من خلال رصد أخطاء معيّنة وتنفيذ مسارك الخاص. تؤدي العمليات الحسّاسة إلى ظهور أربعة أنواع من أخطاء إعادة المصادقة، ويحتوي كل منها على معلومات السياق.

أنماط التنفيذ

مقدّمو خدمات OAuth (مثل Google وApple وFacebook وTwitter وما إلى ذلك):

يمكنك رصد الخطأ واستدعاء reauthenticate(context:) الذي يتعامل تلقائيًا مع عملية OAuth:

do {
  try await authService.deleteUser()
} catch let error as AuthServiceError {
  if case .oauthReauthenticationRequired(let context) = error {
    try await authService.reauthenticate(context: context)
    try await authService.deleteUser() // Retry operation
  }
}

البريد الإلكتروني/كلمة المرور:

التقط الخطأ، واطلب كلمة المرور، وأنشئ بيانات الاعتماد، واستدعِ reauthenticate(with:):

do {
  try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
  if case .emailReauthenticationRequired(let context) = error {
    // Show your password prompt UI
    let password = await promptUserForPassword()
    let credential = EmailAuthProvider.credential(
      withEmail: context.email,
      password: password
    )
    try await authService.reauthenticate(with: credential)
    try await authService.updatePassword(to: newPassword) // Retry
  }
}

الهاتف:

التقط الخطأ، وأثبِت ملكية الهاتف، وأنشئ بيانات الاعتماد، واستدعِ reauthenticate(with:):

do {
  try await authService.deleteUser()
} catch let error as AuthServiceError {
  if case .phoneReauthenticationRequired(let context) = error {
    // Send verification code
    let verificationId = try await authService.verifyPhoneNumber(
      phoneNumber: context.phoneNumber
    )
    // Show your SMS code input UI
    let code = await promptUserForSMSCode()
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId,
      verificationCode: code
    )
    try await authService.reauthenticate(with: credential)
    try await authService.deleteUser() // Retry
  }
}

رابط البريد الإلكتروني:

التقط الخطأ وأرسِل رسالة تأكيد إلكترونية وتعامل مع عنوان URL الوارد:

do {
  try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
  if case .emailLinkReauthenticationRequired(let context) = error {
    // Send verification email
    try await authService.sendEmailSignInLink(
      email: context.email,
      isReauth: true
    )
    // Show your "Check your email" UI
    await showCheckEmailUI()
    // When user taps the link, it opens your app with a URL
    // Handle it in your URL handler:
    // try await authService.handleSignInLink(url: url)
    // The handleSignInLink method automatically completes reauthentication
    try await authService.updatePassword(to: newPassword) // Retry
  }
}

تتضمّن جميع عناصر سياق إعادة المصادقة السمة .displayMessage للنص المعروض للمستخدم.

مقدّمو خدمات OAuth المخصّصون

يمكنك إنشاء موفّري OAuth مخصّصين لخدمات تتجاوز الخدمات المضمّنة:

⚠️ ملاحظة مهمة: يجب إعداد موفّري خدمة OIDC (اتصال OpenID) في إعدادات المصادقة في مشروع Firebase قبل استخدامهم. في Firebase Console، انتقِل إلى المصادقة → طريقة تسجيل الدخول وأضِف موفِّر OIDC باستخدام بيانات الاعتماد المطلوبة (معرّف العميل وسر العميل وعنوان URL الخاص بجهة الإصدار). يجب أيضًا تسجيل معرّف الموارد المنتظمة (URI) لإعادة التوجيه الخاص ببروتوكول OAuth والذي توفّره Firebase في وحدة تحكّم المطوّر الخاصة بموفّر الخدمة. يمكنك الاطّلاع على مستندات Firebase OIDC للحصول على تعليمات مفصّلة حول الإعداد.

import FirebaseAuthSwiftUI
import FirebaseOAuthSwiftUI
import SwiftUI

struct ContentView: View {
  let authService: AuthService

  init() {
    let configuration = AuthConfiguration()
    
    authService = AuthService(configuration: configuration)
      .withOAuthSignIn(
        OAuthProviderSwift(
          providerId: "oidc.line",  // LINE OIDC provider
          scopes: ["profile", "openid", "email"],  // LINE requires these scopes
          displayName: "Sign in with LINE",
          buttonIcon: Image("line-logo"),
          buttonBackgroundColor: .green,
          buttonForegroundColor: .white
        )
      )
      .withOAuthSignIn(
        OAuthProviderSwift(
          providerId: "oidc.custom-provider",
          scopes: ["profile", "openid"],
          displayName: "Sign in with Custom",
          buttonIcon: Image(systemName: "person.circle"),
          buttonBackgroundColor: .purple,
          buttonForegroundColor: .white
        )
      )
  }

  var body: some View {
    AuthPickerView {
      Text("App Content")
    }
    .environment(authService)
  }
}