使用 FirebaseUI 將登入功能新增至 iOS 應用程式

適用於 SwiftUI 的 FirebaseUI 是以 Firebase Authentication 為基礎建構的現代化程式庫,以 SwiftUI 為優先考量,可為應用程式提供預先建構的登入流程。

SwiftUI 適用的 FirebaseUI 具有下列優點:

  • 預設 UI:使用 AuthPickerView 新增完整的登入流程。
  • 可自訂:在自己的版面配置中算繪預設按鈕,或打造完全自訂的體驗。
  • 匿名帳戶連結:選擇升級匿名使用者,而非取代。
  • 帳戶管理:內建註冊、密碼復原和帳戶管理流程。
  • 多個提供者:電子郵件/密碼、電子郵件連結、電話驗證、Apple、Google、Facebook、Twitter,以及標準 OAuth2/OIDC 提供者。
  • 新式驗證功能:內建支援多重驗證 (MFA) 和 async/await API。

事前準備

安裝

SwiftUI 適用的 FirebaseUI 以 Swift 套件的形式提供。如要在 Xcode 專案中安裝套件,請按照下列步驟操作:

  1. 在 Xcode 中,依序點選「File」>「Add Package Dependencies」

  2. 輸入套件網址:

    https://github.com/firebase/FirebaseUI-iOS
    
  3. 在「Dependency Rule」選單中,選取「Up to Next Major Version」,並將最低版本設為最新版本

  4. 按一下「Add Package」(新增套件),然後選取要新增至專案的程式庫。

    您一律需要下列程式庫。其中包含核心依附元件,以及電子郵件/密碼登入和電子郵件連結登入的支援功能。

    • FirebaseAuthSwiftUI

    如要支援其他登入方法,請選取下列一或多個程式庫:

    • FirebaseAppleSwiftUI (使用 Apple 帳戶登入)
    • FirebaseGoogleSwiftUI (使用 Google 帳戶登入)
    • FirebaseFacebookSwiftUI (使用 Facebook 帳戶登入)
    • FirebasePhoneAuthSwiftUI (手機驗證)
    • FirebaseTwitterSwiftUI (使用 Twitter 帳戶登入)
    • FirebaseOAuthSwiftUI (標準 OAuth 和 OIDC 提供者,例如 GitHub、Microsoft、Yahoo)
  5. 按一下「新增套件」,安裝所選程式庫。

  6. 在頂層的初始設定中設定 FirebaseUI 的 AuthServiceView並使用環境將服務傳遞至子檢視畫面。

    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. 在呼叫 FirebaseApp.configure() 的相同 AppDelegate 中,將邏輯新增至 application(_:open:options:) 方法,在開啟的網址是 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:)

Apple

如要使用「使用 Apple 帳戶登入」功能,請按照下列步驟操作:

  1. 設定「使用 Apple 帳戶登入」:

    1. 在 Apple 開發人員網站的「Certificates, Identifiers & Profiles」頁面,為應用程式啟用「使用 Apple 登入」功能。
    2. 按照「為網站設定『使用 Apple 登入』功能」一文第一節的說明,將網站與應用程式建立關聯。系統提示時,請將下列網址註冊為返回網址:
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      您可以在Firebase控制台設定頁面取得 Firebase 專案 ID。 完成後,請記下新的服務 ID,下節會用到。
    3. 建立「使用 Apple 帳戶登入」私密金鑰。 下一節會需要新的私密金鑰和金鑰 ID。
    4. 如果您使用 Firebase Authentication 的任何功能傳送電子郵件給使用者,包括電子郵件連結登入、電子郵件地址驗證、帳戶變更撤銷等,請設定 Apple 私人電子郵件轉發服務,並註冊 noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (或自訂電子郵件範本網域),這樣 Apple 就能將 Firebase Authentication 傳送的電子郵件轉發至匿名 Apple 電子郵件地址。
  2. Firebase 控制台的「安全性」>「驗證」>「登入方式」部分,啟用「Apple」

    • 指定您在上一節建立的服務 ID。
    • 在「OAuth code flow configuration」(OAuth 程式碼流程設定) 部分,指定您的 Apple 團隊 ID,以及您在上一節中建立的私密金鑰和金鑰 ID。
  3. 在 Xcode 中開啟專案編輯器的「Signing & Capabilities」(簽署與功能) 區段,然後新增「Sign in with Apple」(使用 Apple 登入) 功能。

  4. AuthService 執行個體中註冊供應商:

    let authService = AuthService()
      .withAppleSignIn()
    

Google

如要使用 Google 登入,請按照下列步驟操作:

  1. Firebase 控制台的「安全性」>「驗證」>「登入方式」部分,啟用「Google」供應商。

  2. 下載專案的 GoogleService-Info.plist 檔案副本,並複製到 Xcode 專案。覆寫所有現有版本。

  3. 在 Xcode 專案中新增自訂網址架構:

    1. 開啟專案設定:按一下左側樹狀檢視畫面中的專案名稱。在「目標」部分選取應用程式,然後選取「資訊」分頁標籤,並展開「網址類型」部分。

    2. 按一下「+」按鈕,然後為反向用戶端 ID 新增網址架構。如要找出這個值,請開啟 GoogleService-Info.plist 設定檔,然後尋找 REVERSED_CLIENT_ID 鍵。複製該金鑰的值,然後貼到設定頁面的「URL Schemes」(網址架構) 方塊中。其他欄位則維持不變。

      完成後,您的設定應如下所示 (但會包含應用程式專屬值):

  4. AuthService 執行個體中註冊供應商:

    let authService = AuthService()
      .withGoogleSignIn()
    

Facebook

如要使用 Facebook 登入功能,請按照下列步驟操作:

  1. 請按照 Meta for Developers 網站上的操作說明,設定 Facebook Login for iOS SDK。略過最後一個步驟「將 Facebook 登入功能新增至程式碼」。

  2. Firebase 控制台的「安全性」>「驗證」>「登入方式」部分,啟用「Facebook」供應商。您需要 Meta for Developers 網站提供的 Facebook 應用程式 ID 和應用程式密鑰。

  3. AuthService 執行個體中註冊供應商:

    let authService = AuthService()
     .withFacebookSignIn()
    

電話號碼

如要使用電話驗證,請按照下列步驟操作:

  1. Firebase 控制台的「安全性」>「驗證」>「登入方式」部分,啟用「電話」供應商。

  2. 按照「開始接收無聲通知」一節的說明,為應用程式設定 APNs。

  3. 在 Xcode 專案中新增自訂網址架構:

    1. 開啟專案設定:按一下左側樹狀檢視畫面中的專案名稱。在「目標」部分選取應用程式,然後選取「資訊」分頁標籤,並展開「網址類型」部分。

    2. 按一下「+」按鈕,然後將已編碼的應用程式 ID 新增為網址架構。 如要找出這個值,請在 Firebase 控制台中依序開啟「設定」>「一般」

      完成後,您的設定應如下所示 (但會包含應用程式專屬值):

  4. 在呼叫 FirebaseApp.configure() 的相同 AppDelegate 中,新增 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 開發人員網站上的操作說明,產生 X API 憑證。

  2. Firebase 控制台的「安全性」>「驗證」>「登入方式」部分,啟用「Twitter」供應商。您需要 X 開發人員控制台的 API 金鑰和 API 密鑰。

  3. AuthService 執行個體中註冊供應商:

    let authService = AuthService()
      .withTwitterSignIn()
    

標準 OAuth2 和 OIDC 供應商

FirebaseUI 也支援內建的 OAuth 提供者,例如 GitHub、Microsoft 和 Yahoo,以及在 Firebase 驗證中設定的自訂 OIDC 提供者。

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

如果是自訂 OIDC 提供者,請先在 Firebase 驗證中設定提供者,然後使用提供者 ID 和按鈕設定建立 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)

使用預先建構的驗證檢視畫面

SwiftUI 適用的 FirebaseUI 提供 AuthPickerView,這是一個預先建構的驗證 UI,可處理整個驗證流程。這是為應用程式新增驗證功能最簡單的方法。

範例

以下是 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. Sheet Presentation:驗證 UI 會以模式對話方塊的形式顯示
  2. 內建導覽:自動在登入、密碼救援、多重驗證、電子郵件連結和電話號碼驗證畫面之間導覽
  3. 驗證狀態管理:根據 authService.authenticationState 自動在驗證 UI 和內容之間切換
  4. 透過 isPresented 控制:設定 authService.isPresented = true/false,控制何時顯示驗證表單

條理明確的行為

預設 AuthPickerView 會針對如何處理多種複雜情境提供建議:

1. 解決帳戶衝突問題

發生帳戶衝突時 (例如使用已連結至其他帳戶的憑證登入),AuthPickerView 會自動處理:

  • 匿名升級衝突:如果啟用 shouldAutoUpgradeAnonymousUsers,且匿名升級期間發生衝突,系統會自動登出匿名使用者,並使用新憑證登入。
  • 其他衝突:如果非匿名帳戶之間發生憑證衝突,系統會儲存待處理的憑證,並在成功登入後嘗試連結。

這是由 NavigationStack 層級套用的 AccountConflictModifier 處理。

2. 多重驗證 (MFA)

在設定中啟用 MFA 後:

  • 登入時自動偵測是否需要多重驗證
  • 顯示適當的 MFA 解決畫面 (簡訊或 TOTP)
  • 處理多重驗證註冊和管理流程
  • 支援簡訊和限時動態密碼 (TOTP) 因素
3. 處理錯誤

預設檢視畫面包含內建錯誤處理機制:

  • 在快訊對話方塊中顯示清楚易懂的錯誤訊息
  • 自動篩除內部處理的錯誤 (例如取消錯誤、自動處理的衝突)
  • 透過 StringUtils 使用本地化錯誤訊息
  • 錯誤會透過 reportError 環境金鑰傳播

設定電子郵件連結登入後:

  • 自動將電子郵件地址儲存在應用程式儲存空間中
  • 處理電子郵件中的深層連結導覽
  • 管理完整的電子郵件驗證流程
  • 支援透過電子郵件連結升級匿名使用者
5. 匿名使用者自動升級

啟用「shouldAutoUpgradeAnonymousUsers」後:

  • 自動嘗試將匿名帳戶連結至新的登入憑證
  • 升級匿名工作階段,而非取代,以保留使用者資料
  • 妥善處理升級衝突
6. 預設檢視畫面中的重新驗證

執行刪除帳戶、更新密碼或取消註冊多重驗證因素等敏感操作時,必須先完成驗證。使用預設檢視畫面時,系統會根據使用者的登入供應商自動處理重新驗證。

當敏感作業需要重新驗證時,預設檢視畫面會自動執行下列操作:

  • OAuth 提供者 (Google、Apple、Facebook、Twitter 等):顯示要求使用者確認的快訊,然後自動取得新憑證並完成作業。

  • 電子郵件地址/密碼:顯示提示,要求使用者先輸入密碼再繼續。

  • 電子郵件連結:顯示要求傳送驗證電子郵件的快訊,然後顯示內含電子郵件檢查指示的頁面。使用者輕觸電子郵件中的連結,完成重新驗證。

  • 電話:顯示說明需要驗證的快訊,然後顯示簡訊驗證碼的表單。

重新驗證成功後,系統會自動重試作業。使用 AuthPickerView 或內建帳戶管理檢視畫面 (UpdatePasswordViewSignedInView 等) 時,不需要額外程式碼。

進階:建構自訂驗證檢視畫面

如要進一步控管 UI 或導覽流程,您可以自行建立自訂驗證檢視畫面,同時仍使用 AuthService 進行驗證邏輯。

您可以透過多種方式,將 FirebaseUI 元素與自訂邏輯混合使用。以下章節提供幾個自訂方法的範例。

做法 1:使用 registerProvider() 的自訂按鈕

如要完全掌控按鈕外觀,可以建立自己的自訂 AuthProviderUI 實作項目,包裝任何供應商並傳回自訂按鈕檢視畫面。

建立自訂供應商 UI

以下舉例說明如何建立自訂的 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. MFA 處理:檢查 SignInOutcome 是否為 .mfaRequired,並手動處理 MFA 解決方案
  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
  }
}

電子郵件連結:

擷取錯誤、傳送驗證電子郵件,並處理傳入的網址:

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 提供者:

⚠️ 重要事項:必須先在 Firebase 專案的「驗證」設定中設定 OIDC (OpenID Connect) 提供者,才能使用這些提供者。在 Firebase 控制台中,前往「Authentication」→「Sign-in method」,然後使用必要憑證 (用戶端 ID、用戶端密鑰、核發者網址) 新增 OIDC 供應商。您也必須在供應商的開發人員控制台中,註冊 Firebase 提供的 OAuth 重新導向 URI。如需詳細設定說明,請參閱 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)
  }
}