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 avec des opinions : 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 : mettez à niveau les utilisateurs anonymes au lieu de les remplacer (facultatif).
  • Gestion de compte : flux intégrés pour l'inscription, la récupération de mot de passe et la gestion de compte.
  • Plusieurs fournisseurs : 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 moderne : compatibilité intégrée avec l'authentification multifacteur (MFA) et les API async/await.

Avant de commencer

Installation

FirebaseUI pour SwiftUI est fourni en tant que 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 Règle de dépendance, sélectionnez Jusqu'à la prochaine version majeure et définissez la version minimale sur la dernière version.

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

    La bibliothèque suivante est toujours requise. Il inclut les dépendances principales, 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 prendre en charge 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 et Yahoo)
  5. Cliquez sur Ajouter un package pour installer les bibliothèques sélectionnées.

  6. Configurez 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 des 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 les 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 par lien envoyé par e-mail :

  1. Dans la section Sécurité > Authentification > Méthode de connexion de la console Firebase, activez Adresse e-mail/Mot de passe, puis activez la connexion par lien envoyé par e-mail. Notez que la connexion par e-mail ou mot de passe doit être activée pour utiliser la connexion par lien d'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 dans lequel 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 comme URL de renvoi :
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      Vous trouverez 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. Vous en 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 son ID dans la section suivante.
    4. Si vous utilisez des fonctionnalités de Firebase Authentication qui envoient des e-mails aux utilisateurs, y compris la connexion par lien e-mail, la validation d'adresse e-mail, la révocation de modification de compte, etc., configurez le service de relais de messagerie privé d'Apple et enregistrez noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (ou le domaine de votre 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 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 Signature et capacités de l'éditeur de projet et ajoutez la capacité 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. Remplacez 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 CIBLES, puis l'onglet Infos et développez la section 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 la zone URL Schemes (Schémas d'URL) sur la page de configuration. Ne modifiez pas les autres champs.

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

  4. Enregistrez le fournisseur dans votre instance AuthService :

    let authService = AuthService()
      .withGoogleSignIn()
    

Facebook

Pour utiliser la connexion via Facebook :

  1. Configurez le SDK Facebook Login pour iOS en suivant les instructions sur le site Meta for Developers. Ignorez la dernière étape, "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 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 APNs pour votre application en suivant les instructions de 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 CIBLES, puis l'onglet Infos et développez la section Types d'URL.

    2. Cliquez sur le bouton +, puis ajoutez votre 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 terminée, votre configuration devrait ressembler à ce qui suit (mais avec vos valeurs spécifiques à l'application) :

  4. Dans le même AppDelegate dans lequel 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 pour l'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 depuis 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 orientée qui gère l'intégralité du flux d'authentification pour vous. Il s'agit du 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 composant d'image, consultez Ajouter des images à votre projet Xcode.

  • customStringsBundle : utiliser des chaînes personnalisées à partir du Bundle spécifié. Il est utilisé 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, d'authentification multifactorielle, de lien d'e-mail et de validation du numéro de téléphone
  3. Gestion de l'état de l'authentification : bascule automatiquement entre l'UI d'authentification et votre contenu en fonction de authService.authenticationState
  4. Contrôle via isPresented : contrôlez le moment où la feuille d'authentification s'affiche en définissant authService.isPresented = true/false.

Comportements avisés

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

1. Résolution des conflits de compte

En cas de conflit de compte (par exemple, si vous vous connectez avec des identifiants déjà associés à un autre compte), AuthPickerView le gère automatiquement :

  • Conflits de mise à niveau anonymes : 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 nouvelles identifiants.
  • Autres conflits : en cas de conflit 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.

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

2. Authentification multifacteur (MFA)

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

  • Détecte automatiquement quand l'authentification multifactorielle est requise lors de la connexion
  • Affiche les écrans de résolution de l'authentification multifacteur appropriés (SMS ou TOTP)
  • Gère les flux d'inscription et de gestion de l'authentification multifactorielle
  • Compatible avec les facteurs de mot de passe à usage unique (TOTP) et de code envoyé par SMS
3. Traiter les erreurs

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

  • Affiche des messages d'erreur faciles à comprendre dans des boîtes de dialogue d'alerte
  • Filtre automatiquement les erreurs gérées en interne (par exemple, les erreurs d'annulation et 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 envoyé par e-mail est configurée :

  • Stocke automatiquement l'adresse e-mail dans le stockage de l'application
  • Gère la navigation par lien profond depuis un e-mail
  • Gère l'intégralité du flux de validation des adresses e-mail
  • Prise en charge des mises à niveau pour les utilisateurs anonymes via un lien dans un e-mail
5. Migration automatique des utilisateurs anonymes

Lorsque shouldAutoUpgradeAnonymousUsers est activé :

  • Tente automatiquement d'associer les comptes anonymes aux nouveaux identifiants de connexion
  • Conserve 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, comme la suppression de comptes, la modification de mots de passe ou la désinscription de facteurs MFA, nécessitent une authentification récente. Lorsque vous utilisez des 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 affichent automatiquement :

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

  • E-mail/Mot de passe : affichez 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 présente une feuille contenant des instructions pour vérifier l'e-mail. L'utilisateur appuie sur le lien contenu dans l'e-mail pour finaliser la réauthentification.

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

L'opération fait automatiquement l'objet de plusieurs tentatives après une réauthentification réussie. Aucun code supplémentaire n'est requis lorsque vous utilisez AuthPickerView ou les vues de gestion de compte intégrées (UpdatePasswordView, SignedInView, etc.).

Options avancées : créer des vues d'authentification personnalisées

Si vous avez besoin de contrôler davantage l'UI ou le flux de navigation, vous pouvez créer vos propres vues d'authentification personnalisées tout en tirant parti de 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 d'approches de personnalisation que vous pouvez utiliser.

Approche 1 : Boutons personnalisés avec registerProvider()

Pour contrôler entièrement l'apparence du bouton, 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 comment créer un bouton Twitter personnalisé, par exemple :

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 des vues personnalisées

Vous pouvez utiliser AuthService.renderButtons() et ignorer 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 contrôler entièrement le 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 l'AMF : vérifiez SignInOutcome pour .mfaRequired et gérez la résolution de l'AMF 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 lors des opérations d'authentification asynchrone en observant authService.authenticationState.
  6. Réauthentification : gérez les erreurs de réauthentification pour les opérations sensibles (voir Ré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 détectant 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.) :

Identifiez 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 les 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 vers l'adresse e-mail :

Détectez l'erreur, envoyez un 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 destiné à 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 à Authentification → Méthode 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 pour les développeurs 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)
  }
}