Dodawanie logowania do aplikacji na iOS za pomocą FirebaseUI

FirebaseUI dla SwiftUI to nowoczesna biblioteka oparta na Firebase Authentication, która udostępnia gotowe procesy logowania w aplikacji.

FirebaseUI dla SwiftUI ma te zalety:

  • Domyślny interfejs użytkownika: dodaj kompletny proces logowania za pomocą AuthPickerView.
  • Możliwość dostosowania: możesz renderować domyślne przyciski we własnym układzie lub utworzyć w pełni niestandardowe środowisko.
  • Anonimowe łączenie kont: opcjonalnie możesz uaktualnić konta użytkowników anonimowych zamiast je zastępować.
  • Zarządzanie kontem: wbudowane procesy rejestracji, odzyskiwania hasła i zarządzania kontem.
  • Wielu dostawców: adres e-mail i hasło, link do adresu e-mail, uwierzytelnianie przez telefon, Apple, Google, Facebook, Twitter oraz standardowi dostawcy OAuth2/OIDC.
  • Nowoczesne funkcje uwierzytelniania: wbudowana obsługa uwierzytelniania wielopoziomowego (MFA) i interfejsów API async/await.

Zanim zaczniesz

  • Dodaj Firebase do projektu Apple. Pamiętaj, aby wykonać kroki dotyczące SwiftUI.

  • Upewnij się, że aplikacja jest przeznaczona na system iOS 17 lub nowszy.

Instalacja

FirebaseUI dla SwiftUI jest dostarczany jako pakiet Swift. Aby zainstalować pakiet w projekcie Xcode, wykonaj te czynności:

  1. W Xcode kliknij File > Add Package Dependencies (Plik > Dodaj zależności pakietu).

  2. Wpisz adres URL pakietu:

    https://github.com/firebase/FirebaseUI-iOS
    
  3. W menu Dependency Rule (Reguła zależności) wybierz Up to Next Major Version (Do następnej wersji głównej) i ustaw minimalną wersję na najnowszą wersję.

  4. Kliknij Add Package (Dodaj pakiet), a następnie wybierz biblioteki, które chcesz dodać do projektu.

    Zawsze wymagana jest ta biblioteka: Zawiera podstawowe zależności, a także obsługę logowania się za pomocą adresu e-mail i hasła oraz linku e-mail.

    • FirebaseAuthSwiftUI

     Jeśli chcesz obsługiwać inne metody logowania, wybierz też co najmniej 1 z tych bibliotek:

    • FirebaseAppleSwiftUI (Zaloguj się przez Apple)
    • FirebaseGoogleSwiftUI (Zaloguj się przez Google)
    • FirebaseFacebookSwiftUI (Zaloguj się przez Facebooka)
    • FirebasePhoneAuthSwiftUI (uwierzytelnianie przez telefon)
    • FirebaseTwitterSwiftUI (Zaloguj się przez Twittera)
    • FirebaseOAuthSwiftUI (standardowi dostawcy OAuth i OIDC, tacy jak GitHub, Microsoft, Yahoo)
  5. Kliknij Add Package (Dodaj pakiet), aby zainstalować wybrane biblioteki.

  6. Skonfiguruj AuthService FirebaseUI w inicjatorze najwyższego poziomu View i przekaż usługę do widoków podrzędnych za pomocą środowiska.

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

Konfigurowanie metod logowania

Każda z obsługiwanych metod logowania wymaga wykonania dodatkowych czynności konfiguracyjnych. Rozwiń sekcje poniżej i postępuj zgodnie z instrukcjami, aby skonfigurować poszczególnych dostawców.

adres e-mail i hasło,

  1. W sekcji Bezpieczeństwo > Uwierzytelnianie > Metoda logowania konsoli Firebase włącz dostawcę E-mail/hasło.

  2. Zarejestruj dostawcę w instancji AuthService:

    let authService = AuthService()
      .withEmailSignIn()
    

Aby użyć logowania bez hasła za pomocą linku wysłanego e-mailem:

  1. W sekcji Bezpieczeństwo > Uwierzytelnianie > Metoda logowania konsoli Firebase włącz opcję E-mail/hasło, a następnie włącz logowanie za pomocą linku w e-mailu. Pamiętaj, że aby korzystać z logowania za pomocą linku w e-mailu, musisz włączyć logowanie za pomocą adresu e-mail lub hasła.

  2. Dodaj domenę linku do Autoryzowanych domen.

  3. Skonfiguruj ActionCodeSettings, przekaż do AuthConfiguration i zarejestruj dostawcę:

    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. W tym samym obiekcie AppDelegate, w którym wywołujesz FirebaseApp.configure(), dodaj do metody application(_:open:options:) logikę, która zwraca true, gdy otwarty adres URL jest linkiem logowania Firebase Authentication. Ta logika wskazuje, że link został obsłużony przez FirebaseUI i nie powinien być przetwarzany przez inne moduły obsługi linków.

    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. Jeśli tworzysz widoki niestandardowe, wywołaj authService.handleSignInLink(url:), gdy link otworzy aplikację.

Apple

Aby korzystać z funkcji Zaloguj się przez Apple:

  1. Skonfiguruj funkcję Zaloguj się przez Apple:

    1. Włącz funkcję Zaloguj się przez Apple w aplikacji na stronie Certificates, Identifiers & Profiles (Certyfikaty, identyfikatory i profile) w witrynie Apple dla deweloperów.
    2. Powiąż witrynę z aplikacją zgodnie z opisem w pierwszej sekcji artykułu Configure Sign In with Apple for the web (Konfigurowanie funkcji Zaloguj się przez Apple w internecie). Gdy pojawi się prośba, zarejestruj ten adres URL jako adres URL zwrotu:
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      Identyfikator projektu w Firebase znajdziesz na stronie ustawień konsoli Firebase. Gdy skończysz, zanotuj nowy identyfikator usługi, który będzie Ci potrzebny w następnej sekcji.
    3. Utwórz klucz prywatny funkcji Zaloguj się przez Apple. W następnej sekcji będziesz potrzebować nowego klucza prywatnego i identyfikatora klucza.
    4. Jeśli używasz funkcji Firebase Authentication, które wysyłają e-maile do użytkowników, w tym logowania za pomocą linku w e-mailu, weryfikacji adresu e-mail, unieważnienia zmiany konta i innych, skonfiguruj usługę prywatnego przekazywania poczty e-mail Apple i zarejestruj noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (lub domenę niestandardowego szablonu e-maila), aby firma Apple mogła przekazywać e-maile wysyłane przez Firebase Authentication na anonimowe adresy e-mail Apple.
  2. W sekcji Bezpieczeństwo > Uwierzytelnianie > Metoda logowania konsoli Firebase włącz Apple.

    • Podaj identyfikator usługi utworzony w poprzedniej sekcji.
    • W sekcji Konfiguracja procesu kodu OAuth podaj identyfikator zespołu Apple oraz klucz prywatny i identyfikator klucza utworzone w poprzedniej sekcji.
  3. W Xcode otwórz sekcję Signing & Capabilities (Podpisywanie i funkcje) w edytorze projektu i dodaj funkcję Sign in with Apple (Zaloguj się przez Apple).

  4. Zarejestruj dostawcę w instancji AuthService:

    let authService = AuthService()
      .withAppleSignIn()
    

Google

Aby używać funkcji Logowanie przez Google:

  1. W sekcji Bezpieczeństwo > Uwierzytelnianie > Metoda logowania konsoli Firebase włącz dostawcę Google.

  2. Pobierz nową kopię pliku GoogleService-Info.plist projektu i skopiuj ją do projektu Xcode. Zastąp wszystkie istniejące wersje nową.

  3. Dodaj niestandardowe schematy URI adresów URL do projektu Xcode:

    1. Otwórz konfigurację projektu: kliknij nazwę projektu w widoku drzewa po lewej stronie. Wybierz aplikację w sekcji TARGETS (Cele), a następnie kliknij kartę Info (Informacje) i rozwiń sekcję URL Types (Typy adresów URL).

    2. Kliknij przycisk + i dodaj schemat URI adresu URL dla odwróconego identyfikatora klienta. Aby znaleźć tę wartość, otwórz plik konfiguracji GoogleService-Info.plist i poszukaj klucza REVERSED_CLIENT_ID. Skopiuj wartość tego klucza i wklej ją w polu Schematy adresów URL na stronie konfiguracji. Pozostałe pola pozostaw bez zmian.

      Po zakończeniu konfiguracja powinna wyglądać podobnie do tej (ale z wartościami specyficznymi dla Twojej aplikacji):

  4. Zarejestruj dostawcę w instancji AuthService:

    let authService = AuthService()
      .withGoogleSignIn()
    

Facebook

Aby używać logowania przez Facebooka:

  1. Skonfiguruj pakiet SDK Facebook Login for iOS, postępując zgodnie z instrukcjami na stronie Meta for Developers. Pomiń ostatni krok „Add Facebook Login to Your Code” (Dodaj logowanie przez Facebooka do kodu).

  2. W sekcji Zabezpieczenia > Uwierzytelnianie > Metoda logowania konsoli Firebase włącz dostawcę Facebook. Potrzebne będą identyfikator aplikacji na Facebooku i tajny klucz aplikacji z witryny Meta for Developers.

  3. Zarejestruj dostawcę w instancji AuthService:

    let authService = AuthService()
     .withFacebookSignIn()
    

Numer telefonu

Aby użyć uwierzytelniania przez telefon:

  1. W sekcji Zabezpieczenia > Uwierzytelnianie > Metoda logowania konsoli Firebase włącz dostawcę Telefon.

  2. Skonfiguruj APN dla aplikacji zgodnie z instrukcjami w sekcji Zacznij otrzymywać ciche powiadomienia.

  3. Dodaj niestandardowe schematy URI adresów URL do projektu Xcode:

    1. Otwórz konfigurację projektu: kliknij nazwę projektu w widoku drzewa po lewej stronie. Wybierz aplikację w sekcji TARGETS (Cele), a następnie kliknij kartę Info (Informacje) i rozwiń sekcję URL Types (Typy adresów URL).

    2. Kliknij przycisk + i dodaj zakodowany identyfikator aplikacji jako schemat URI adresu URL. Aby znaleźć tę wartość, w konsoli Firebase otwórz Ustawienia > Ogólne.

      Po zakończeniu konfiguracja powinna wyglądać podobnie do tej (ale z wartościami specyficznymi dla Twojej aplikacji):

  4. W tym samym pliku AppDelegate, w którym wywołujesz FirebaseApp.configure(), dodaj moduły obsługi tokenów 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. Zarejestruj dostawcę w instancji AuthService:

    let authService = AuthService()
      .withPhoneSignIn()
    

Twitter (X)

Aby użyć logowania przez Twittera:

  1. Wygeneruj dane logowania do interfejsu API X, postępując zgodnie z instrukcjami na stronie X Developers.

  2. W sekcji Bezpieczeństwo > Uwierzytelnianie > Metoda logowania konsoli Firebase włącz dostawcę Twitter. Będziesz potrzebować klucza interfejsu API i tajnego klucza interfejsu API z konsoli X Developer.

  3. Zarejestruj dostawcę w instancji AuthService:

    let authService = AuthService()
      .withTwitterSignIn()
    

Standardowi dostawcy OAuth2 i OIDC

FirebaseUI obsługuje też wbudowanych dostawców OAuth, takich jak GitHub, Microsoft i Yahoo, a także niestandardowych dostawców OIDC skonfigurowanych w Uwierzytelnianiu Firebase.

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

W przypadku niestandardowych dostawców OIDC najpierw skonfiguruj dostawcę w Firebase Authentication, a następnie utwórz element OAuthProviderSwift z identyfikatorem dostawcy i konfiguracją przycisku:

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)

Korzystanie z gotowego widoku uwierzytelniania

FirebaseUI dla SwiftUI udostępnia AuthPickerView, czyli gotowy, dostosowany interfejs uwierzytelniania, który obsługuje cały proces uwierzytelniania. To najprostszy sposób na dodanie uwierzytelniania do aplikacji.

Przykład

Oto przykład użycia AuthPickerView z wieloma dostawcami i opcjami konfiguracji:

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

Typowe dostosowywanie

Chociaż wszystkie parametry AuthConfiguration są opcjonalne, większość aplikacji dostosowuje co najmniej te ustawienia:

let configuration = AuthConfiguration(
    logo: ImageResource.exampleLogoAsset,
    customStringsBundle: .main,
    tosUrl: URL(string: "https://example.com/tos"),
    privacyPolicyUrl: URL(string: "https://example.com/privacy"),
)
  • logo: obraz logo wyświetlany w arkuszu uwierzytelniania. Informacje o dołączaniu własnego komponentu z obrazem znajdziesz w artykule o dodawaniu obrazów do projektu Xcode.

  • customStringsBundle: użyj niestandardowych ciągów znaków z określonego pakietu. Służy on do lokalizacji, ale też do dostosowywania domyślnych ciągów znaków używanych przez AuthPickerView. Aby na przykład ustawić komunikat wyświetlany u góry arkusza uwierzytelniania, utwórz plik Localizable.strings o następującej treści:

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

Co zawiera gotowy widok

Gdy używasz AuthPickerView, otrzymujesz:

  1. Prezentacja arkusza: interfejs uwierzytelniania jest wyświetlany jako arkusz modalny.
  2. Wbudowana nawigacja: automatyczna nawigacja między ekranami logowania, odzyskiwania hasła, MFA, linku do e-maila i weryfikacji telefonicznej.
  3. Zarządzanie stanem uwierzytelniania: automatycznie przełącza się między interfejsem uwierzytelniania a treściami na podstawie authService.authenticationState
  4. Sterowanie za pomocą isPresented: możesz kontrolować, kiedy pojawia się arkusz uwierzytelniania, ustawiając authService.isPresented = true/false.

Zachowania oparte na opiniach

Domyślny AuthPickerView ma określone podejście do obsługi kilku złożonych scenariuszy:

1. Rozwiązywanie konfliktów dotyczących kont

Gdy wystąpi konflikt kont (np. logowanie się za pomocą danych logowania, które są już połączone z innym kontem), AuthPickerView automatycznie go rozwiązuje:

  • Konflikty podczas anonimowego uaktualniania: jeśli opcja shouldAutoUpgradeAnonymousUsers jest włączona i podczas anonimowego uaktualniania wystąpi konflikt, system automatycznie wyloguje anonimowego użytkownika i zaloguje się przy użyciu nowych danych logowania.
  • Inne konflikty: w przypadku konfliktów danych logowania między kontami nieanonimowymi system przechowuje oczekujące dane logowania i próbuje je połączyć po udanym zalogowaniu.

Odpowiada za to AccountConflictModifier zastosowany na poziomie NavigationStack.

2. Uwierzytelnianie wielopoziomowe (MFA)

Gdy w konfiguracji jest włączone uwierzytelnianie wieloskładnikowe:

  • Automatycznie wykrywa, kiedy podczas logowania wymagane jest uwierzytelnianie wieloskładnikowe.
  • wyświetla odpowiednie ekrany uwierzytelniania wieloskładnikowego (SMS lub TOTP);
  • Obsługuje procesy rejestracji i zarządzania MFA
  • Obsługuje czynniki SMS-owe i hasła jednorazowe generowane na podstawie czasu (TOTP)
3. Obsługa błędów

Widoki domyślne obejmują wbudowaną obsługę błędów:

  • wyświetlać w oknach alertów przyjazne dla użytkownika komunikaty o błędach;
  • Automatycznie odfiltrowuje błędy, które są obsługiwane wewnętrznie (np.błędy anulowania, automatycznie obsługiwane konflikty).
  • Używa zlokalizowanych komunikatów o błędach w polu StringUtils
  • Błędy są propagowane przez klucz środowiska reportError

Gdy skonfigurowane jest logowanie za pomocą linku e-mail:

  • Automatycznie zapisuje adres e-mail w pamięci aplikacji.
  • Obsługuje nawigację za pomocą precyzyjnych linków z e-maila.
  • Zarządza całym procesem potwierdzania adresu e-mail
  • Obsługuje uaktualnienia anonimowych użytkowników za pomocą linku z e-maila.
5. Automatyczne uaktualnianie użytkowników anonimowych

Gdy shouldAutoUpgradeAnonymousUsers jest włączone:

  • Automatycznie próbuje połączyć konta anonimowe z nowymi danymi logowania.
  • zachowuje dane użytkownika, uaktualniając sesje anonimowe, zamiast je zastępować;
  • Prawidłowo obsługuje konflikty uaktualnień.
6. Ponowne uwierzytelnianie w widokach domyślnych

Operacje poufne, takie jak usuwanie kont, aktualizowanie haseł czy wyrejestrowywanie czynników MFA, wymagają niedawnego uwierzytelnienia. W przypadku widoków domyślnych ponowne uwierzytelnianie jest obsługiwane automatycznie na podstawie dostawcy logowania użytkownika.

Gdy operacja związana z poufnymi danymi wymaga ponownego uwierzytelnienia, widoki domyślne automatycznie:

  • Dostawcy OAuth (Google, Apple, Facebook, Twitter itp.): Wyświetl alert z prośbą o potwierdzenie, a następnie automatycznie uzyskaj nowe dane logowania i dokończ operację.

  • E-mail/hasło: wyświetl arkusz z prośbą o wpisanie hasła przed kontynuowaniem.

  • Link w e-mailu: wyświetl alert z prośbą o wysłanie e-maila weryfikacyjnego, a następnie arkusz z instrukcjami dotyczącymi sprawdzenia poczty e-mail. Użytkownik klika link w e-mailu, aby dokończyć ponowne uwierzytelnianie.

  • Telefon: wyświetl alert z informacją, że wymagana jest weryfikacja, a następnie arkusz do weryfikacji kodu SMS.

Operacja zostanie automatycznie ponowiona po pomyślnym ponownym uwierzytelnieniu. Jeśli używasz AuthPickerView lub wbudowanych widoków zarządzania kontem (UpdatePasswordView, SignedInView itp.), nie jest wymagany żaden dodatkowy kod.

Zaawansowane: tworzenie niestandardowych widoków uwierzytelniania

Jeśli potrzebujesz większej kontroli nad interfejsem lub nawigacją, możesz utworzyć własne niestandardowe widoki uwierzytelniania, nadal korzystając z AuthService do obsługi logiki uwierzytelniania.

Elementy FirebaseUI można łączyć z niestandardową logiką na wiele sposobów. W poniższych sekcjach znajdziesz przykłady niektórych metod dostosowywania, których możesz użyć.

Metoda 1. Przyciski niestandardowe z registerProvider()

Aby mieć pełną kontrolę nad wyglądem przycisku, możesz utworzyć własną implementację AuthProviderUI, która opakowuje dowolnego dostawcę i zwraca widok niestandardowego przycisku.

Tworzenie niestandardowego interfejsu dostawcy

Oto przykład, jak utworzyć niestandardowy przycisk Twittera:

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

Przykład uproszczonego przycisku niestandardowego

Możesz też utworzyć prostsze przyciski niestandardowe dla dowolnego dostawcy:

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

Ta metoda działa w przypadku wszystkich dostawców: Google, Apple, Twittera, Facebooka, telefonu i OAuth. Wystarczy utworzyć niestandardowy widok przycisku i umieścić go w klasie zgodnej z AuthProviderUI.

Podejście 2. Przyciski domyślne z widokami niestandardowymi

Możesz użyć AuthService.renderButtons() i pominąć AuthPickerView, aby renderować domyślne przyciski uwierzytelniania, jednocześnie udostępniając własny układ i nawigację:

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

Podejście 3. Widoki niestandardowe z niestandardową nawigacją

Aby mieć pełną kontrolę nad całym procesem, możesz pominąć AuthPickerView i utworzyć własny system nawigacji:

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

Ważne uwagi dotyczące widoków niestandardowych

Podczas tworzenia widoków niestandardowych musisz samodzielnie obsłużyć kilka rzeczy, które AuthPickerView obsługuje automatycznie:

  1. Konflikty dotyczące kont: wdróż własną strategię rozwiązywania konfliktów za pomocą AuthServiceError.accountConflict.
  2. Obsługa uwierzytelniania wielopoziomowego: sprawdź SignInOutcome pod kątem .mfaRequired i ręcznie rozwiąż problem z uwierzytelnianiem wielopoziomowym.
  3. Uaktualnienia anonimowych użytkowników: obsługa łączenia kont anonimowych, jeśli włączona jest funkcja shouldAutoUpgradeAnonymousUsers
  4. Stan nawigacji: zarządzanie nawigacją między różnymi ekranami uwierzytelniania (weryfikacja telefoniczna, odzyskiwanie hasła itp.).
  5. Stany wczytywania: wyświetlaj wskaźniki wczytywania podczas asynchronicznych operacji uwierzytelniania, obserwując authService.authenticationState.
  6. Ponowne uwierzytelnianie: obsługa błędów ponownego uwierzytelniania w przypadku operacji związanych z danymi poufnymi (patrz Ponowne uwierzytelnianie w widokach niestandardowych poniżej).

Ponowne uwierzytelnianie w widokach niestandardowych

Podczas tworzenia widoków niestandardowych obsługuj ponowne uwierzytelnianie, przechwytując określone błędy i wdrażając własny proces. Operacje poufne powodują wystąpienie 4 rodzajów błędów ponownego uwierzytelniania, z których każdy zawiera informacje o kontekście.

Wzorce implementacji

Dostawcy OAuth (Google, Apple, Facebook, Twitter itp.):

Przechwyć błąd i wywołaj funkcję reauthenticate(context:), która automatycznie obsługuje proces 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
  }
}

Email/Password:

Przechwyć błąd, poproś o hasło, utwórz dane logowania i wywołaj 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
  }
}

Telefon:

Przechwyć błąd, zweryfikuj telefon, utwórz dane logowania i wywołaj 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
  }
}

Link do e-maila:

Przechwyć błąd, wyślij e-maila weryfikacyjnego i obsłuż przychodzący adres 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
  }
}

Wszystkie obiekty kontekstu ponownego uwierzytelniania zawierają właściwość .displayMessage dla tekstu widocznego dla użytkownika.

Niestandardowi dostawcy OAuth

Możesz tworzyć niestandardowych dostawców OAuth dla usług innych niż wbudowane:

⚠️ Ważne: dostawcy OIDC (OpenID Connect) muszą zostać skonfigurowani w ustawieniach uwierzytelniania projektu w Firebase, zanim będzie można ich używać. W konsoli Firebase otwórz Authentication → Sign-in method (Uwierzytelnianie → Metoda logowania) i dodaj dostawcę OIDC z wymaganymi danymi logowania (identyfikator klienta, tajny klucz klienta, URL wystawcy). Musisz też zarejestrować identyfikator URI przekierowania OAuth podany przez Firebase w konsoli dewelopera dostawcy. Szczegółowe instrukcje konfiguracji znajdziesz w dokumentacji 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)
  }
}