Ajouter la connexion à votre application iOS avec FirebaseUI

FirebaseUI pour SwiftUI est une bibliothèque moderne, axée sur SwiftUI et basée sur Firebase Authentication, qui fournit des flux de connexion prédéfinis à votre application.

FirebaseUI pour SwiftUI présente les avantages suivants :

  • UI par défaut : ajoutez un flux de connexion complet avec AuthPickerView.
  • Personnalisable : affichez les boutons par défaut dans votre propre mise en page ou créez une expérience entièrement personnalisée.
  • Association de comptes anonymes : vous pouvez mettre à niveau les utilisateurs anonymes au lieu de les remplacer.
  • Gestion des comptes : flux intégrés pour l'inscription, la récupération de mot de passe et la gestion des comptes.
  • Plusieurs fournisseurs : adresse e-mail/mot de passe, lien par e-mail, authentification par téléphone, Apple, Google, Facebook, Twitter et fournisseurs OAuth2/OIDC standards.
  • Fonctionnalités d'authentification modernes : compatibilité intégrée avec l'authentification multifacteur (MFA) et les API async/await.

Avant de commencer

Installation

FirebaseUI pour SwiftUI est fourni sous forme de package Swift. Pour installer le package dans votre projet Xcode, procédez comme suit :

  1. Dans Xcode, cliquez sur File > Add Package Dependencies (Fichier > Ajouter des dépendances de package).

  2. Saisissez l'URL du package :

    https://github.com/firebase/FirebaseUI-iOS
    
  3. Dans le menu Dependency Rule (Règle de dépendance), sélectionnez Up to Next Major Version (Jusqu'à la prochaine version majeure) et définissez la version minimale sur la dernière version.

  4. Cliquez sur Add Package (Ajouter un package), puis sélectionnez les bibliothèques à ajouter à votre projet.

    La bibliothèque suivante est toujours requise. Il inclut les dépendances de base, ainsi que la prise en charge de la connexion par adresse e-mail et mot de passe, et de la connexion par lien envoyé par e-mail.

    • FirebaseAuthSwiftUI

     Si vous souhaitez accepter d'autres méthodes de connexion, sélectionnez également une ou plusieurs des bibliothèques suivantes :

    • FirebaseAppleSwiftUI (Se connecter avec Apple)
    • FirebaseGoogleSwiftUI (Se connecter avec Google)
    • FirebaseFacebookSwiftUI (Se connecter avec Facebook)
    • FirebasePhoneAuthSwiftUI (authentification par téléphone)
    • FirebaseTwitterSwiftUI (Se connecter avec Twitter)
    • FirebaseOAuthSwiftUI (fournisseurs OAuth et OIDC standards tels que GitHub, Microsoft, Yahoo)
  5. Cliquez sur Add Package (Ajouter un package) pour installer les bibliothèques sélectionnées.

  6. Configurez le AuthService de FirebaseUI dans l'initialiseur de votre View de premier niveau et transmettez le service aux vues enfants à l'aide de l'environnement.

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

Configurer les méthodes de connexion

Chacune des méthodes de connexion compatibles nécessite des étapes de configuration supplémentaires. Développez les sections ci-dessous et suivez les instructions pour configurer des fournisseurs individuels.

Adresse e-mail et mot de passe

  1. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez le fournisseur Adresse e-mail/Mot de passe.

  2. Enregistrez le fournisseur dans votre instance AuthService :

    let authService = AuthService()
      .withEmailSignIn()
    

Pour utiliser la connexion sans mot de passe avec un lien envoyé par e-mail :

  1. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez l'option Adresse e-mail/Mot de passe, puis activez la connexion par lien d'e-mail. Notez que la connexion par adresse e-mail ou mot de passe doit être activée pour utiliser la connexion par lien envoyé par e-mail.

  2. Ajoutez le domaine du lien à Domaines autorisés.

  3. Configurez ActionCodeSettings, transmettez-le à AuthConfiguration et enregistrez le fournisseur :

    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. Dans le même AppDelegate que celui où vous appelez FirebaseApp.configure(), ajoutez une logique à la méthode application(_:open:options:) qui renvoie true lorsque l'URL ouverte est un lien de connexion Firebase Authentication. Cette logique indique que le lien a été géré par FirebaseUI et ne doit pas être traité par d'autres gestionnaires de liens.

    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. Si vous créez des vues personnalisées, appelez authService.handleSignInLink(url:) lorsque le lien ouvre votre application.

Apple

Pour utiliser Se connecter avec Apple :

  1. Configurez Se connecter avec Apple :

    1. Activez la connexion avec Apple pour votre application sur la page Certificates, Identifiers & Profiles (Certificats, identifiants et profils) du site pour les développeurs d'Apple.
    2. Associez votre site Web à votre application comme décrit dans la première section de Configurer la connexion avec Apple pour le Web. Lorsque vous y êtes invité, enregistrez l'URL suivante en tant qu'URL de retour :
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      Vous pouvez obtenir l'ID de votre projet Firebase sur la page des paramètres de la console Firebase. Lorsque vous avez terminé, notez votre nouvel ID de service, dont vous aurez besoin dans la section suivante.
    3. Créez une clé privée pour "Se connecter avec Apple". Vous aurez besoin de votre nouvelle clé privée et de votre ID de clé dans la section suivante.
    4. Si vous utilisez l'une des fonctionnalités de Firebase Authentication qui envoient des e-mails aux utilisateurs, y compris la connexion par lien d'e-mail, la validation de l'adresse e-mail, la révocation des modifications de compte, etc., configurez le service de relais de messagerie privé d'Apple et enregistrez noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (ou votre domaine de modèle d'e-mail personnalisé) afin qu'Apple puisse relayer les e-mails envoyés par Firebase Authentication vers des adresses e-mail Apple anonymisées.
  2. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez Apple.

    • Spécifiez l'ID de service que vous avez créé dans la section précédente.
    • Dans la section OAuth code flow configuration (Configuration du flux de code OAuth), spécifiez votre ID d'équipe Apple, ainsi que la clé privée et l'ID de clé que vous avez créés dans la section précédente.
  3. Dans Xcode, ouvrez la section Signing & Capabilities (Signature et fonctionnalités) de l'éditeur de projet, puis ajoutez la fonctionnalité Sign in with Apple (Se connecter avec Apple).

  4. Enregistrez le fournisseur dans votre instance AuthService :

    let authService = AuthService()
      .withAppleSignIn()
    

Google

Pour utiliser Google Sign-In :

  1. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez le fournisseur Google.

  2. Téléchargez une nouvelle copie du fichier GoogleService-Info.plist de votre projet et copiez-la dans votre projet Xcode. Écrasez toutes les versions existantes par la nouvelle.

  3. Ajoutez des schémas d'URL personnalisés à votre projet Xcode :

    1. Ouvrez la configuration de votre projet : cliquez sur le nom du projet dans l'arborescence de gauche. Sélectionnez votre application dans la section TARGETS (CIBLES), puis sélectionnez l'onglet Info (Informations) et développez la section URL Types (Types d'URL).

    2. Cliquez sur le bouton +, puis ajoutez un schéma d'URL pour votre ID client inversé. Pour trouver cette valeur, ouvrez le fichier de configuration GoogleService-Info.plist et recherchez la clé REVERSED_CLIENT_ID. Copiez la valeur de cette clé et collez-la dans le champ Schémas d'URL de la page de configuration. Ne modifiez pas les autres champs.

      Une fois l'opération terminée, votre configuration doit ressembler à ce qui suit (mais avec les valeurs spécifiques à votre application) :

  4. Enregistrez le fournisseur dans votre instance AuthService :

    let authService = AuthService()
      .withGoogleSignIn()
    

Facebook

Pour utiliser la connexion Facebook :

  1. Configurez le SDK Facebook Login pour iOS en suivant les instructions sur le site Meta for Developers. Ignorez la dernière étape, "Add Facebook Login to Your Code" (Ajouter la connexion Facebook à votre code).

  2. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez le fournisseur Facebook. Vous aurez besoin de l'ID et du code secret de l'application Facebook, disponibles sur le site Meta for Developers.

  3. Enregistrez le fournisseur dans votre instance AuthService :

    let authService = AuthService()
     .withFacebookSignIn()
    

Numéro de téléphone

Pour utiliser l'authentification par téléphone :

  1. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez le fournisseur Téléphone.

  2. Configurez les APN pour votre application comme indiqué dans la section Commencer à recevoir des notifications silencieuses.

  3. Ajoutez des schémas d'URL personnalisés à votre projet Xcode :

    1. Ouvrez la configuration de votre projet : cliquez sur le nom du projet dans l'arborescence de gauche. Sélectionnez votre application dans la section TARGETS (CIBLES), puis sélectionnez l'onglet Info (Informations) et développez la section URL Types (Types d'URL).

    2. Cliquez sur le bouton +, puis ajoutez l'ID d'application encodé en tant que schéma d'URL. Pour trouver cette valeur, ouvrez Paramètres > Général dans la console Firebase.

      Une fois l'opération terminée, votre configuration doit ressembler à ce qui suit (mais avec les valeurs spécifiques à votre application) :

  4. Dans le même AppDelegate que celui où vous appelez FirebaseApp.configure(), ajoutez des gestionnaires de jetons 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. Enregistrez le fournisseur dans votre instance AuthService :

    let authService = AuthService()
      .withPhoneSignIn()
    

Twitter (X)

Pour utiliser la connexion Twitter :

  1. Générez des identifiants d'API X en suivant les instructions sur le site X Developers.

  2. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez le fournisseur Twitter. Vous aurez besoin de la clé API et du code secret de l'API de la console X Developer.

  3. Enregistrez le fournisseur dans votre instance AuthService :

    let authService = AuthService()
      .withTwitterSignIn()
    

Fournisseurs OAuth2 et OIDC standards

FirebaseUI est également compatible avec les fournisseurs OAuth intégrés tels que GitHub, Microsoft et Yahoo, ainsi qu'avec les fournisseurs OIDC personnalisés configurés dans Firebase Authentication.

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

Pour les fournisseurs OIDC personnalisés, configurez d'abord le fournisseur dans Firebase Authentication, puis créez un OAuthProviderSwift avec l'ID de votre fournisseur et la configuration du bouton :

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)

Utiliser la vue d'authentification prédéfinie

FirebaseUI pour SwiftUI fournit AuthPickerView, une UI d'authentification prédéfinie et personnalisée qui gère l'intégralité du flux d'authentification pour vous. C'est le moyen le plus simple d'ajouter l'authentification à votre application.

Exemple

Voici un exemple d'utilisation de AuthPickerView, avec plusieurs fournisseurs et options de configuration :

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

Personnalisation type

Bien que tous les paramètres AuthConfiguration soient facultatifs, la plupart des applications personnalisent au moins les paramètres suivants :

let configuration = AuthConfiguration(
    logo: ImageResource.exampleLogoAsset,
    customStringsBundle: .main,
    tosUrl: URL(string: "https://example.com/tos"),
    privacyPolicyUrl: URL(string: "https://example.com/privacy"),
)
  • logo : image du logo affichée sur la feuille d'authentification. Pour savoir comment inclure votre propre asset d'image, consultez Ajouter des images à votre projet Xcode.

  • customStringsBundle : utilisez des chaînes personnalisées du Bundle spécifié. Cette option est utilisée pour la localisation, mais aussi pour personnaliser les chaînes par défaut utilisées par AuthPickerView. Par exemple, pour définir le message affiché en haut de la feuille d'authentification, créez Localizable.strings avec le contenu suivant :

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

Éléments inclus dans la vue prédéfinie

Lorsque vous utilisez AuthPickerView, vous bénéficiez des avantages suivants :

  1. Présentation de la feuille : l'UI d'authentification s'affiche sous forme de feuille modale.
  2. Navigation intégrée : navigation automatique entre les écrans de connexion, de récupération de mot de passe, de MFA, de lien par e-mail et de validation du numéro de téléphone
  3. Gestion de l'état d'authentification : bascule automatiquement entre l'UI d'authentification et votre contenu en fonction de authService.authenticationState
  4. Contrôle via isPresented : contrôlez l'affichage de la feuille d'authentification en définissant authService.isPresented = true/false.

Comportements avisés

La valeur par défaut de AuthPickerView est basée sur la façon de gérer plusieurs scénarios complexes :

1. Résolution des conflits de compte

Lorsqu'un conflit de compte se produit (par exemple, lors de la connexion avec un identifiant déjà associé à un autre compte), AuthPickerView le gère automatiquement :

  • Conflits de mise à niveau anonyme : si shouldAutoUpgradeAnonymousUsers est activé et qu'un conflit se produit lors de la mise à niveau anonyme, le système déconnecte automatiquement l'utilisateur anonyme et se connecte avec les nouveaux identifiants.
  • Autres conflits : pour les conflits d'identifiants entre des comptes non anonymes, le système stocke l'identifiant en attente et tente de l'associer une fois la connexion établie.

Ceci est géré par le AccountConflictModifier appliqué au niveau de NavigationStack.

2. Authentification multifacteur (MFA)

Lorsque l'authentification multifacteur est activée dans votre configuration :

  • Détecte automatiquement quand l'authentification multifacteur est requise lors de la connexion
  • Présente les écrans de résolution MFA appropriés (SMS ou TOTP)
  • Gère les flux d'enregistrement et de gestion de l'authentification multifacteur
  • Prend en charge les facteurs de mot de passe à usage unique (OTP) par SMS et par jeton (TOTP)
3. Gestion des exceptions

Les vues par défaut incluent la gestion des erreurs intégrée :

  • Affiche des messages d'erreur conviviaux dans des boîtes de dialogue d'alerte.
  • Filtre automatiquement les erreurs gérées en interne (par exemple, les erreurs d'annulation, les conflits gérés automatiquement)
  • Utilise des messages d'erreur localisés via StringUtils
  • Les erreurs sont propagées via la clé d'environnement reportError.

Lorsque la connexion par lien par e-mail est configurée :

  • Stocke automatiquement l'adresse e-mail dans l'espace de stockage de l'application
  • Gère la navigation par lien profond à partir d'un e-mail
  • Gère l'intégralité du flux de validation de l'adresse e-mail
  • Permet de mettre à niveau les utilisateurs anonymes via un lien envoyé par e-mail.
5. Mise à niveau automatique des utilisateurs anonymes

Lorsque shouldAutoUpgradeAnonymousUsers est activé :

  • Tente d'associer automatiquement les comptes anonymes à de nouveaux identifiants de connexion
  • Préserve les données utilisateur en mettant à niveau les sessions anonymes au lieu de les remplacer
  • Gère les conflits de mise à niveau de façon optimale
6. Réauthentification dans les vues par défaut

Les opérations sensibles telles que la suppression de comptes, la mise à jour de mots de passe ou la désinscription de facteurs MFA nécessitent une authentification récente. Lorsque vous utilisez les vues par défaut, la réauthentification est gérée automatiquement en fonction du fournisseur de connexion de l'utilisateur.

Lorsqu'une opération sensible nécessite une réauthentification, les vues par défaut effectuent automatiquement les actions suivantes :

  • Fournisseurs OAuth (Google, Apple, Facebook, Twitter, etc.) : Affichez une alerte demandant à l'utilisateur de confirmer, puis obtenez automatiquement de nouveaux identifiants et terminez l'opération.

  • E-mail/Mot de passe : affiche une feuille invitant l'utilisateur à saisir son mot de passe avant de continuer.

  • Lien par e-mail : affiche une alerte demandant d'envoyer un e-mail de validation, puis une feuille contenant des instructions pour consulter l'e-mail. L'utilisateur appuie sur le lien figurant dans l'e-mail pour terminer la réauthentification.

  • Téléphone : affichez une alerte expliquant que la validation est nécessaire, puis présentez une feuille pour la validation du code par SMS.

L'opération est automatiquement relancée une fois la réauthentification effectuée. Aucun code supplémentaire n'est requis lorsque vous utilisez AuthPickerView ou les vues de gestion de compte intégrées (UpdatePasswordView, SignedInView, etc.).

Avancé : Créer des vues d'authentification personnalisées

Si vous avez besoin de mieux contrôler l'interface utilisateur ou le flux de navigation, vous pouvez créer vos propres vues d'authentification personnalisées tout en exploitant le AuthService pour la logique d'authentification.

Vous pouvez combiner des éléments de FirebaseUI avec une logique personnalisée de différentes manières. Les sections suivantes contiennent des exemples de certaines des approches de personnalisation que vous pouvez utiliser.

Approche 1 : Boutons personnalisés avec registerProvider()

Pour contrôler entièrement l'apparence des boutons, vous pouvez créer votre propre implémentation AuthProviderUI personnalisée qui encapsule n'importe quel fournisseur et renvoie votre vue de bouton personnalisée.

Créer une UI de fournisseur personnalisée

Voici un exemple de création d'un bouton Twitter personnalisé :

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

Exemple de bouton personnalisé simplifié

Vous pouvez également créer des boutons personnalisés plus simples pour n'importe quel fournisseur :

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

Cette approche fonctionne pour tous les fournisseurs : Google, Apple, Twitter, Facebook, téléphone et fournisseurs OAuth. Il vous suffit de créer votre vue de bouton personnalisée et de l'encapsuler dans une classe conforme à AuthProviderUI.

Approche 2 : Boutons par défaut avec vues personnalisées

Vous pouvez utiliser AuthService.renderButtons() et contourner AuthPickerView pour afficher les boutons d'authentification par défaut tout en fournissant votre propre mise en page et navigation :

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

Approche 3 : Vues personnalisées avec navigation personnalisée

Pour un contrôle total sur l'ensemble du flux, vous pouvez contourner AuthPickerView et créer votre propre système de navigation :

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

Remarques importantes concernant les vues personnalisées

Lorsque vous créez des vues personnalisées, vous devez gérer vous-même plusieurs éléments que AuthPickerView gère automatiquement :

  1. Conflits de compte : implémentez votre propre stratégie de résolution des conflits à l'aide de AuthServiceError.accountConflict.
  2. Gestion de la MFA : vérifiez SignInOutcome pour .mfaRequired et gérez la résolution de la MFA manuellement.
  3. Mises à niveau des utilisateurs anonymes : gérez l'association des comptes anonymes si shouldAutoUpgradeAnonymousUsers est activé.
  4. État de la navigation : gérez la navigation entre les différents écrans d'authentification (validation du numéro de téléphone, récupération du mot de passe, etc.).
  5. États de chargement : affichez des indicateurs de chargement pendant les opérations d'authentification asynchrones en observant authService.authenticationState.
  6. Nouvelle authentification : gérez les erreurs de nouvelle authentification pour les opérations sensibles (voir Nouvelle authentification dans les vues personnalisées ci-dessous).

Réauthentification dans les vues personnalisées

Lorsque vous créez des vues personnalisées, gérez la réauthentification en interceptant des erreurs spécifiques et en implémentant votre propre flux. Les opérations sensibles génèrent quatre types d'erreurs de réauthentification, chacune contenant des informations contextuelles.

Modèles d'implémentation

Fournisseurs OAuth (Google, Apple, Facebook, Twitter, etc.) :

Interceptez l'erreur et appelez reauthenticate(context:), qui gère automatiquement le flux 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
  }
}

Adresse e-mail/Mot de passe :

Interceptez l'erreur, demandez le mot de passe, créez des identifiants et appelez 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
  }
}

Téléphone :

Interceptez l'erreur, validez le numéro de téléphone, créez un identifiant et appelez 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
  }
}

Lien de l'e-mail :

Interceptez l'erreur, envoyez l'e-mail de validation et gérez l'URL entrante :

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

Tous les objets de contexte de réauthentification incluent une propriété .displayMessage pour le texte visible par l'utilisateur.

Fournisseurs OAuth personnalisés

Vous pouvez créer des fournisseurs OAuth personnalisés pour des services autres que ceux intégrés :

⚠️ Important : Les fournisseurs OIDC (OpenID Connect) doivent être configurés dans les paramètres d'authentification de votre projet Firebase avant de pouvoir être utilisés. Dans la console Firebase, accédez à Authentication → Sign-in method (Authentification → Mode de connexion), puis ajoutez votre fournisseur OIDC avec les identifiants requis (ID client, code secret du client, URL de l'émetteur). Vous devez également enregistrer l'URI de redirection OAuth fourni par Firebase dans la console de développement de votre fournisseur. Pour obtenir des instructions de configuration détaillées, consultez la documentation Firebase sur 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)
  }
}