با استفاده از FirebaseUI، ورود به سیستم را به برنامه iOS خود اضافه کنید

FirebaseUI برای SwiftUI یک کتابخانه مدرن و مبتنی بر SwiftUI است که بر پایه Firebase Authentication ساخته شده و جریان‌های ورود از پیش ساخته شده را برای برنامه شما فراهم می‌کند.

FirebaseUI برای SwiftUI مزایای زیر را دارد:

  • رابط کاربری پیش‌فرضِ نظرمحور : یک جریان ورود کامل با AuthPickerView اضافه کنید.
  • قابل تنظیم : دکمه‌های پیش‌فرض را در طرح‌بندی دلخواه خود رندر کنید، یا یک تجربه کاملاً سفارشی بسازید.
  • اتصال حساب‌های کاربری ناشناس : به صورت اختیاری، به جای جایگزینی کاربران ناشناس، آنها را ارتقا دهید.
  • مدیریت حساب : جریان‌های داخلی برای ثبت نام، بازیابی رمز عبور و مدیریت حساب.
  • ارائه دهندگان متعدد : ایمیل/رمز عبور، لینک ایمیل، احراز هویت تلفنی، اپل، گوگل، فیسبوک، توییتر و ارائه دهندگان استاندارد OAuth2/OIDC.
  • ویژگی‌های احراز هویت مدرن : پشتیبانی داخلی از احراز هویت چند عاملی (MFA) و APIهای async/await.

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

نصب

FirebaseUI برای SwiftUI به عنوان یک بسته Swift ارائه شده است. برای نصب این بسته در پروژه Xcode خود، موارد زیر را انجام دهید:

  1. در Xcode، روی File > Add Package Dependencies کلیک کنید.

  2. آدرس اینترنتی بسته را وارد کنید:

    https://github.com/firebase/FirebaseUI-iOS
    
  3. در منوی Dependency Rule ، گزینه Up to Next Major Version را انتخاب کنید و حداقل نسخه را روی آخرین نسخه تنظیم کنید.

  4. روی «افزودن بسته» کلیک کنید، سپس کتابخانه‌هایی را که می‌خواهید به پروژه خود اضافه کنید، انتخاب کنید.

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

    • FirebaseAuthSwiftUI

    اگر می‌خواهید از روش‌های ورود دیگری پشتیبانی کنید، یک یا چند کتابخانه زیر را نیز انتخاب کنید:

    • FirebaseAppleSwiftUI (ورود با اپل)
    • FirebaseGoogleSwiftUI (ورود با گوگل)
    • FirebaseFacebookSwiftUI (ورود با فیسبوک)
    • FirebasePhoneAuthSwiftUI (احراز هویت از طریق تلفن)
    • FirebaseTwitterSwiftUI (ورود با توییتر)
    • FirebaseOAuthSwiftUI (ارائه‌دهندگان استاندارد OAuth و OIDC مانند GitHub، Microsoft، Yahoo)
  5. برای نصب کتابخانه‌های انتخاب شده، روی «افزودن بسته» کلیک کنید.

  6. AuthService مربوط به FirebaseUI را در مقداردهی اولیه‌ی View سطح بالای خود پیکربندی کنید و سرویس را با استفاده از محیط به نماهای فرزند منتقل کنید.

    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. از بخش Security > Authentication > Sign-in method در کنسول Firebase ، ارائه دهنده ایمیل/رمز عبور را فعال کنید.

  2. ارائه دهنده را در نمونه AuthService خود ثبت کنید:

    let authService = AuthService()
      .withEmailSignIn()
    

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

  1. از بخش Security > Authentication > Sign-in method در کنسول Firebase ، گزینه Email/Password و سپس ورود از طریق لینک ایمیل را فعال کنید. توجه داشته باشید که برای استفاده از ورود از طریق لینک ایمیل، باید ورود از طریق ایمیل یا رمز عبور فعال باشد.

  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:) اضافه کنید که وقتی URL باز شده یک لینک ورود به Firebase Authentication است، true را برگرداند. این منطق نشان می‌دهد که لینک توسط 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:) را فراخوانی کنید.

اپل

برای استفاده از ورود با اپل:

  1. پیکربندی ورود به سیستم با اپل:

    1. برای برنامه خود، ورود به سیستم با اپل را در صفحه گواهینامه‌ها، شناسه‌ها و پروفایل‌ها در سایت توسعه‌دهندگان اپل فعال کنید.
    2. وب‌سایت خود را همانطور که در بخش اول پیکربندی ورود با اپل برای وب توضیح داده شده است، با برنامه خود مرتبط کنید. در صورت درخواست، URL زیر را به عنوان URL بازگشت ثبت کنید:
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      می‌توانید شناسه پروژه Firebase خود را در صفحه تنظیمات کنسول Firebase دریافت کنید. پس از اتمام کار، شناسه سرویس جدید خود را یادداشت کنید، که در بخش بعدی به آن نیاز خواهید داشت.
    3. با کلید خصوصی اپل، یک حساب کاربری ایجاد کنید . در بخش بعدی به کلید خصوصی جدید و شناسه کلید خود نیاز خواهید داشت.
    4. اگر از هر یک از ویژگی‌های Firebase Authentication که به کاربران ایمیل ارسال می‌کند، از جمله ورود از طریق لینک ایمیل، تأیید آدرس ایمیل، لغو تغییر حساب و موارد دیگر استفاده می‌کنید، سرویس رله ایمیل خصوصی اپل را پیکربندی کنید و noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com (یا دامنه الگوی ایمیل سفارشی خود) را ثبت کنید تا اپل بتواند ایمیل‌های ارسالی توسط Firebase Authentication را به آدرس‌های ایمیل ناشناس اپل رله کند.
  2. از بخش Security > Authentication > Sign-in method در کنسول Firebase ، گزینه Apple را فعال کنید.

    • شناسه سرویسی که در بخش قبل ایجاد کردید را مشخص کنید.
    • در بخش پیکربندی جریان کد OAuth ، شناسه تیم اپل و کلید خصوصی و شناسه کلید خود را که در بخش قبلی ایجاد کرده‌اید، مشخص کنید.
  3. در Xcode، بخش Signing & Capabilities را از ویرایشگر پروژه باز کنید و قابلیت Sign in with Apple را اضافه کنید.

  4. ارائه دهنده را در نمونه AuthService خود ثبت کنید:

    let authService = AuthService()
      .withAppleSignIn()
    

گوگل

برای استفاده از ورود به سیستم گوگل:

  1. از بخش Security > Authentication > Sign-in method در کنسول Firebase ، ارائه دهنده گوگل را فعال کنید.

  2. یک کپی جدید از فایل GoogleService-Info.plist پروژه خود دانلود کنید و آن را در پروژه Xcode خود کپی کنید. نسخه‌های موجود را با نسخه جدید جایگزین کنید.

  3. طرح‌های URL سفارشی را به پروژه Xcode خود اضافه کنید:

    1. پیکربندی پروژه خود را باز کنید: روی نام پروژه در نمای درختی سمت چپ کلیک کنید. برنامه خود را از بخش TARGETS انتخاب کنید، سپس تب Info را انتخاب کنید و بخش URL Types را باز کنید.

    2. Click the + button, and add a URL scheme for your reversed client ID. To find this value, open the GoogleService-Info.plist configuration file, and look for the REVERSED_CLIENT_ID key. Copy the value of that key, and paste it into the URL Schemes box on the configuration page. Leave the other fields untouched.

      پس از تکمیل، پیکربندی شما باید چیزی شبیه به موارد زیر باشد (اما با مقادیر خاص برنامه شما):

  4. ارائه دهنده را در نمونه AuthService خود ثبت کنید:

    let authService = AuthService()
      .withGoogleSignIn()
    

فیسبوک

برای استفاده از ورود به فیسبوک:

  1. با دنبال کردن دستورالعمل‌های موجود در سایت Meta for Developers، افزونه‌ی ورود به سیستم فیس‌بوک برای iOS SDK را راه‌اندازی کنید. از مرحله‌ی آخر، یعنی «افزودن ورود به سیستم فیس‌بوک به کد خود»، صرف نظر کنید.

  2. از بخش Security > Authentication > Sign-in method در کنسول Firebase ، ارائه‌دهنده‌ی فیسبوک را فعال کنید. برای این کار به Facebook App ID و App Secret از سایت Meta for Developers نیاز خواهید داشت.

  3. ارائه دهنده را در نمونه AuthService خود ثبت کنید:

    let authService = AuthService()
     .withFacebookSignIn()
    

شماره تلفن

برای استفاده از احراز هویت تلفنی:

  1. از بخش Security > Authentication > Sign-in method در کنسول Firebase ، ارائه دهنده تلفن (Phone provider) را فعال کنید.

  2. APN های برنامه خود را طبق دستورالعمل‌های بخش « شروع دریافت اعلان‌های بی‌صدا» پیکربندی کنید.

  3. طرح‌های URL سفارشی را به پروژه Xcode خود اضافه کنید:

    1. پیکربندی پروژه خود را باز کنید: روی نام پروژه در نمای درختی سمت چپ کلیک کنید. برنامه خود را از بخش TARGETS انتخاب کنید، سپس تب Info را انتخاب کنید و بخش URL Types را باز کنید.

    2. روی دکمه + کلیک کنید و شناسه برنامه رمزگذاری شده خود را به عنوان یک طرح URL اضافه کنید. برای یافتن این مقدار، تنظیمات > عمومی را در کنسول Firebase باز کنید.

      پس از تکمیل، پیکربندی شما باید چیزی شبیه به موارد زیر باشد (اما با مقادیر خاص برنامه شما):

  4. در همان AppDelegate که در آن FirebaseApp.configure() را فراخوانی می‌کنید، کنترل‌کننده‌های توکن APN را اضافه کنید:

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

توییتر (ایکس)

برای استفاده از ورود به توییتر:

  1. با دنبال کردن دستورالعمل‌های موجود در سایت توسعه‌دهندگان X، اعتبارنامه‌های X API را ایجاد کنید.

  2. از بخش Security > Authentication > Sign-in method در کنسول Firebase ، ارائه‌دهنده‌ی Twitter را فعال کنید. به کلید API و رمز API از کنسول X Developer نیاز خواهید داشت.

  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 : از رشته‌های سفارشی از Bundle مشخص شده استفاده می‌کند. این برای محلی‌سازی و همچنین برای سفارشی‌سازی رشته‌های پیش‌فرض مورد استفاده توسط AuthPickerView استفاده می‌شود. برای مثال، برای تنظیم پیام نمایش داده شده در بالای برگه احراز هویت، Localizable.strings با محتویات زیر ایجاد کنید:

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

چه چیزهایی در نمای از پیش ساخته شده وجود دارد؟

وقتی از AuthPickerView استفاده می‌کنید، موارد زیر را دریافت می‌کنید:

  1. ارائه برگه : رابط کاربری احراز هویت به صورت یک برگه مدال ظاهر می‌شود
  2. ناوبری داخلی : پیمایش خودکار بین صفحات ورود به سیستم، بازیابی رمز عبور، MFA، لینک ایمیل و تأیید تلفن
  3. مدیریت وضعیت احراز هویت : به طور خودکار بین رابط کاربری احراز هویت و محتوای شما بر اساس authService.authenticationState جابجا می‌شود.
  4. کنترل از طریق isPresented : با تنظیم authService.isPresented = true/false زمان نمایش برگه احراز هویت را کنترل کنید.

رفتارهای خودسرانه

AuthPickerView پیش‌فرض در مورد نحوه مدیریت چندین سناریوی پیچیده، نظر خود را دارد:

۱. حل اختلاف حساب

وقتی تداخل حساب کاربری رخ می‌دهد (مثلاً ورود به سیستم با اعتبارنامه‌ای که از قبل به حساب دیگری مرتبط است)، AuthPickerView به‌طور خودکار آن را مدیریت می‌کند:

  • تداخل‌های ارتقاء ناشناس : اگر shouldAutoUpgradeAnonymousUsers فعال باشد و در حین ارتقاء ناشناس تداخلی رخ دهد، سیستم به طور خودکار کاربر ناشناس را از سیستم خارج کرده و با اعتبارنامه جدید وارد سیستم می‌شود.
  • سایر تداخل‌ها : برای تداخل‌های اعتبارنامه بین حساب‌های کاربری غیر ناشناس، سیستم اعتبارنامه‌ی در حال بررسی را ذخیره می‌کند و پس از ورود موفقیت‌آمیز، سعی می‌کند آن را پیوند دهد.

این توسط AccountConflictModifier اعمال شده در سطح NavigationStack مدیریت می‌شود.

۲. احراز هویت چند عاملی (MFA)

وقتی MFA در پیکربندی شما فعال است:

  • به طور خودکار تشخیص می‌دهد که چه زمانی هنگام ورود به سیستم، MFA مورد نیاز است
  • صفحه نمایش‌های MFA با وضوح مناسب (SMS یا TOTP) را ارائه می‌دهد.
  • جریان‌های ثبت نام و مدیریت MFA را مدیریت می‌کند.
  • از هر دو فاکتور رمز عبور یکبار مصرف (TOTP) مبتنی بر پیامک و مبتنی بر زمان پشتیبانی می‌کند.
۳. مدیریت خطا

نماهای پیش‌فرض شامل مدیریت خطای داخلی هستند:

  • پیام‌های خطای کاربرپسند را در پنجره‌های هشدار نمایش می‌دهد
  • به طور خودکار خطاهایی را که به صورت داخلی مدیریت می‌شوند فیلتر می‌کند (مثلاً خطاهای لغو، تداخل‌های مدیریت خودکار)
  • از پیام‌های خطای محلی‌شده از طریق StringUtils استفاده می‌کند.
  • خطاها از طریق کلید محیطی reportError منتشر می‌شوند.

وقتی ورود از طریق لینک ایمیل پیکربندی شده باشد:

  • آدرس ایمیل را به طور خودکار در حافظه برنامه ذخیره می‌کند
  • ناوبری عمیق لینک‌ها را از طریق ایمیل مدیریت می‌کند
  • جریان کامل تأیید ایمیل را مدیریت می‌کند
  • پشتیبانی از ارتقاء کاربر ناشناس از طریق لینک ایمیل
۵. ارتقاء خودکار توسط کاربر ناشناس

چه زمانی shouldAutoUpgradeAnonymousUsers فعال شود:

  • به‌طور خودکار تلاش می‌کند حساب‌های ناشناس را با اطلاعات ورود جدید مرتبط کند
  • با ارتقاء به جای جایگزینی جلسات ناشناس، داده‌های کاربر را حفظ می‌کند.
  • تداخل‌های ارتقاء را به خوبی مدیریت می‌کند
۶. احراز هویت مجدد در نماهای پیش‌فرض

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

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

  • ارائه‌دهندگان OAuth (گوگل، اپل، فیس‌بوک، توییتر و غیره) : هشداری را نمایش می‌دهند که از کاربر می‌خواهد تأیید کند، سپس به‌طور خودکار اعتبارنامه‌های جدید را دریافت کرده و عملیات را تکمیل می‌کند.

  • ایمیل/رمز عبور : برگه‌ای را نمایش دهید که از کاربر بخواهد قبل از ادامه، رمز عبور خود را وارد کند.

  • لینک ایمیل : هشداری برای ارسال ایمیل تأیید نمایش داده می‌شود، سپس برگه‌ای حاوی دستورالعمل‌های بررسی ایمیل ارائه می‌شود. کاربر برای تکمیل احراز هویت مجدد، روی لینک موجود در ایمیل خود ضربه می‌زند.

  • تلفن : هشداری را نشان دهید که توضیح می‌دهد تأیید هویت لازم است، سپس برگه‌ای برای تأیید کد پیامکی ارائه دهید.

این عملیات پس از احراز هویت مجدد موفقیت‌آمیز، به طور خودکار دوباره انجام می‌شود. هنگام استفاده از AuthPickerView یا نماهای مدیریت حساب داخلی ( UpdatePasswordView ، SignedInView و غیره) نیازی به کد اضافی نیست.

پیشرفته: ساخت نماهای احراز هویت سفارشی

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

شما می‌توانید عناصر FirebaseUI را به روش‌های مختلفی با منطق سفارشی ترکیب کنید. بخش‌های زیر شامل نمونه‌هایی از برخی از رویکردهای سفارشی‌سازی است که می‌توانید استفاده کنید.

رویکرد ۱: دکمه‌های سفارشی با registerProvider()

برای کنترل کامل بر ظاهر دکمه، می‌توانید پیاده‌سازی AuthProviderUI سفارشی خودتان را ایجاد کنید که هر ارائه‌دهنده‌ای را در بر می‌گیرد و نمای دکمه سفارشی شما را برمی‌گرداند.

ایجاد رابط کاربری ارائه دهنده سفارشی

در اینجا نحوه ایجاد یک دکمه توییتر سفارشی به عنوان مثال آورده شده است:

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

این رویکرد برای همه ارائه‌دهندگان خدمات احراز هویت (Provider) از جمله گوگل، اپل، توییتر، فیس‌بوک، تلفن و ارائه‌دهندگان OAuth کار می‌کند. کافیست نمای دکمه سفارشی خود را ایجاد کرده و آن را در کلاسی مطابق با AuthProviderUI قرار دهید.

رویکرد ۲: دکمه‌های پیش‌فرض با نماهای سفارشی

شما می‌توانید از 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)
  }
}

رویکرد ۳: نماهای سفارشی با ناوبری سفارشی

برای کنترل کامل بر کل جریان، می‌توانید 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. مدیریت MFA : بررسی SignInOutcome برای .mfaRequired و مدیریت دستی وضوح MFA
  3. ارتقاء کاربران ناشناس : در صورت فعال بودن shouldAutoUpgradeAnonymousUsers لینک کردن حساب‌های کاربری ناشناس را مدیریت کنید.
  4. وضعیت ناوبری : مدیریت ناوبری بین صفحات مختلف احراز هویت (تأیید تلفن، بازیابی رمز عبور و غیره)
  5. وضعیت‌های بارگیری : با مشاهده‌ی authService.authenticationState نشانگرهای بارگیری را در طول عملیات احراز هویت ناهمگام نمایش دهید.
  6. احراز هویت مجدد : خطاهای احراز هویت مجدد را برای عملیات حساس مدیریت کنید (به بخش احراز هویت مجدد در نماهای سفارشی در زیر مراجعه کنید)

احراز هویت مجدد در نماهای سفارشی

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

الگوهای پیاده‌سازی

ارائه دهندگان OAuth (گوگل، اپل، فیسبوک، توییتر و غیره):

خطا را دریافت کرده و تابع 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 Connect) قبل از استفاده باید در تنظیمات احراز هویت پروژه Firebase شما پیکربندی شوند. در کنسول Firebase، به مسیر Authentication → Sign-in method بروید و ارائه‌دهنده 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)
  }
}