Как добавить в приложение для iOS возможность входа с помощью FirebaseUI

FirebaseUI для SwiftUI – это современная библиотека, созданная на основе Firebase Authentication и предназначенная для SwiftUI. Она предоставляет готовые потоки входа в приложение.

FirebaseUI для SwiftUI имеет следующие преимущества:

  • Пользовательский интерфейс по умолчанию: добавьте полный процесс входа с помощью AuthPickerView.
  • Настраиваемый. Вы можете разместить кнопки по умолчанию в собственном макете или создать полностью уникальный интерфейс.
  • Связывание анонимных аккаунтов. При необходимости можно преобразовать анонимных пользователей, а не заменять их.
  • Управление аккаунтом. Встроенные процессы регистрации, восстановления пароля и управления аккаунтом.
  • Несколько поставщиков: электронная почта/пароль, ссылка в электронном письме, аутентификация по телефону, Apple, Google, Facebook, Twitter и стандартные поставщики OAuth2/OIDC.
  • Современные функции аутентификации: встроенная поддержка многофакторной аутентификации (МФА) и асинхронных API.

Подготовка

  • Добавьте Firebase в свой проект Apple. Обязательно выполните инструкции для SwiftUI.

  • Убедитесь, что ваше приложение предназначено для iOS 17 или более поздней версии.

Установка

FirebaseUI для SwiftUI предоставляется в виде пакета Swift. Чтобы установить пакет в проект Xcode, выполните следующие действия:

  1. В Xcode нажмите File (Файл) > Add Package Dependencies (Добавить зависимости пакетов).

  2. Введите URL пакета:

    https://github.com/firebase/FirebaseUI-iOS
    
  3. В меню Dependency Rule (Правило зависимости) выберите Up to Next Major Version (До следующей основной версии) и задайте минимальную версию последнего выпуска.

  4. Нажмите Добавить пакет и выберите библиотеки, которые нужно добавить в проект.

    Следующая библиотека требуется всегда. Он включает основные зависимости, а также поддержку входа с помощью адреса электронной почты и пароля и входа по ссылке из письма.

    • FirebaseAuthSwiftUI

    Если вы хотите поддерживать другие способы входа, выберите одну или несколько из следующих библиотек:

    • FirebaseAppleSwiftUI (Войти через аккаунт Apple)
    • FirebaseGoogleSwiftUI (Войти с аккаунтом Google)
    • FirebaseFacebookSwiftUI (Войти с аккаунтом Facebook)
    • FirebasePhoneAuthSwiftUI (аутентификация по телефону)
    • FirebaseTwitterSwiftUI (Войти через аккаунт Twitter)
    • FirebaseOAuthSwiftUI (стандартные поставщики OAuth и OIDC, такие как GitHub, Microsoft, Yahoo)
  5. Нажмите Добавить пакет, чтобы установить выбранные библиотеки.

  6. Настройте AuthService в инициализаторе View и передайте сервис дочерним представлениям с помощью среды.

    import FirebaseAuthSwiftUI
    import SwiftUI
    
    struct ContentView: View {
      let authService: AuthService
    
      init() {
        let configuration = AuthConfiguration()
    
        authService = AuthService(configuration: configuration)
          .withEmailSignIn() // Or whatever sign-in methods you want to support.
                             // See the next section.
      }
    
      var body: some View {
        AuthPickerView { // AuthPickerView (the prebuilt View) or a custom View.
            Text("Welcome to your app!")
        }
        .environment(authService)
      }
    }
    

Как настроить способы входа

Для каждого поддерживаемого способа входа требуется выполнить дополнительные настройки. Разверните разделы ниже и следуйте инструкциям, чтобы настроить отдельных поставщиков.

Адрес электронной почты и пароль

  1. В разделе Безопасность > Аутентификация > Способ входа консоли Firebase включите поставщика Электронная почта/пароль.

  2. Зарегистрируйте поставщика в экземпляре AuthService:

    let authService = AuthService()
      .withEmailSignIn()
    

Чтобы войти в аккаунт без пароля, используя ссылку в письме:

  1. В разделе Безопасность > Аутентификация > Способ входа консоли Firebase включите Электронная почта/пароль, а затем включите вход по ссылке из письма. Обратите внимание, что для входа по ссылке из электронного письма необходимо включить вход по адресу электронной почты или паролю.

  2. Добавьте домен ссылки в раздел Авторизованные домены.

  3. Настройте ActionCodeSettings, передайте его в AuthConfiguration и зарегистрируйте поставщика:

    let actionCodeSettings = ActionCodeSettings()
    actionCodeSettings.handleCodeInApp = true
    actionCodeSettings.url = URL(string: "https://yourapp.firebaseapp.com")
    
    guard let bundleID = Bundle.main.bundleIdentifier else {
      fatalError("Missing bundle identifier for email link authentication setup.")
    }
    actionCodeSettings.setIOSBundleID(bundleID)
    
    let configuration = AuthConfiguration(
      emailLinkSignInActionCodeSettings: actionCodeSettings
    )
    
    let authService = AuthService(configuration: configuration)
      .withEmailLinkSignIn()
    
  4. В том же AppDelegate, в котором вы вызываете FirebaseApp.configure(), добавьте логику в метод application(_:open:options:), которая возвращает true, когда открытый URL является ссылкой для входа Firebase Authentication. Эта логика указывает, что ссылка была обработана FirebaseUI и не должна обрабатываться другими обработчиками ссылок.

    import FacebookCore
    import FirebaseAuth
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        FirebaseApp.configure()
        return true
      }
    
      func application(
        _ application: UIApplication,
        open url: URL,
        options: [UIApplication.OpenURLOptionsKey: Any] = [:]
      ) -> Bool {
        if Auth.auth().canHandle(url) {
          return true
        }
        return false
      }
    }
    
  5. Если вы создаете собственные представления, вызывайте authService.handleSignInLink(url:), когда ссылка открывает ваше приложение.

Apple

Чтобы использовать функцию "Войти через аккаунт Apple", выполните следующие действия:

  1. Настройте функцию "Войти через аккаунт Apple":

    1. Включите функцию "Войти с Apple ID" для своего приложения на странице Certificates, Identifiers & Profiles (Сертификаты, идентификаторы и профили) на сайте для разработчиков Apple.
    2. Свяжите сайт с приложением, как описано в первом разделе статьи Настройка входа с Apple ID для сайта. Когда появится запрос, зарегистрируйте следующий URL как URL возврата:
      https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
      Идентификатор проекта Firebase можно найти на странице настроек консоли Firebase. После этого запишите новый идентификатор сервиса, который понадобится вам в следующем разделе.
    3. Создайте закрытый ключ для входа через Apple. В следующем разделе вам понадобятся новый закрытый ключ и идентификатор ключа.
    4. Если вы используете какие-либо функции Firebase Authentication, которые отправляют пользователям электронные письма, в том числе вход по ссылке, подтверждение адреса электронной почты, отмену изменений аккаунта и другие, настройте частный сервис ретрансляции электронной почты Apple и зарегистрируйте noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (или домен вашего шаблона электронного письма), чтобы Apple могла пересылать письма, отправленные Firebase Authentication, на анонимизированные адреса электронной почты Apple.
  2. В разделе Безопасность > Аутентификация > Способ входа консоли Firebase включите Apple.

    • Укажите идентификатор сервиса, созданный в предыдущем разделе.
    • В разделе Настройка потока кода OAuth укажите идентификатор команды Apple, закрытый ключ и идентификатор ключа, созданные в предыдущем разделе.
  3. В Xcode откройте раздел Signing & Capabilities (Подписание и возможности) в редакторе проекта и добавьте возможность Sign in with Apple (Войти с Apple).

  4. Зарегистрируйте поставщика в экземпляре AuthService:

    let authService = AuthService()
      .withAppleSignIn()
    

Google

Чтобы использовать вход с аккаунтом Google:

  1. В разделе Безопасность > Аутентификация > Способ входа консоли Firebase включите поставщика Google.

  2. Скачайте новую копию файла GoogleService-Info.plist проекта и скопируйте ее в проект Xcode. Перезаписать существующие версии новой.

  3. Добавьте в проект Xcode специальные схемы URL:

    1. Откройте конфигурацию проекта: нажмите на название проекта в древовидном представлении слева. Выберите приложение в разделе ЦЕЛЕВОЙ КОНТЕНТ, откройте вкладку Информация и разверните раздел Типы URL.

    2. Нажмите кнопку + и добавьте схему URL для обратного идентификатора клиента. Чтобы найти это значение, откройте файл конфигурации GoogleService-Info.plist и найдите ключ REVERSED_CLIENT_ID. Скопируйте значение этого ключа и вставьте его в поле Схемы URL на странице конфигурации. Оставьте остальные поля без изменений.

      После этого конфигурация должна выглядеть примерно так (но с вашими значениями):

  4. Зарегистрируйте поставщика в экземпляре AuthService:

    let authService = AuthService()
      .withGoogleSignIn()
    

Facebook

Чтобы использовать вход через Facebook:

  1. Настройте Facebook Login для iOS SDK, следуя инструкциям на сайте Meta for Developers. Пропустите последний шаг "Добавьте вход через Facebook в код".

  2. В разделе Безопасность > Аутентификация > Способ входа консоли Firebase включите поставщика Facebook. Вам понадобятся идентификатор приложения Facebook и секретный ключ приложения, которые можно найти на сайте Meta for Developers.

  3. Зарегистрируйте поставщика в экземпляре AuthService:

    let authService = AuthService()
     .withFacebookSignIn()
    

Номер телефона

Чтобы использовать аутентификацию по телефону:

  1. В разделе Безопасность > Аутентификация > Способ входа консоли Firebase включите поставщика Телефон.

  2. Настройте APNs для приложения, следуя инструкциям в разделе Как начать получать тихие уведомления.

  3. Добавьте в проект Xcode специальные схемы URL:

    1. Откройте конфигурацию проекта: нажмите на название проекта в древовидном представлении слева. Выберите приложение в разделе ЦЕЛЕВОЙ КОНТЕНТ, откройте вкладку Информация и разверните раздел Типы URL.

    2. Нажмите кнопку + и добавьте зашифрованный идентификатор приложения в качестве схемы URL. Чтобы найти это значение, откройте Настройки > Общие в консоли Firebase.

      После этого конфигурация должна выглядеть примерно так (но с вашими значениями):

  4. В том же методе AppDelegate, в котором вы вызываете FirebaseApp.configure(), добавьте обработчики токенов APNs:

    import FacebookCore
    import FirebaseAuth
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        FirebaseApp.configure()
        return true
      }
    
      func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
      ) {
        #if DEBUG
        Auth.auth().setAPNSToken(deviceToken, type: .sandbox)
        #else
        Auth.auth().setAPNSToken(deviceToken, type: .prod)
        #endif
      }
    }
    
  5. Зарегистрируйте поставщика в экземпляре AuthService:

    let authService = AuthService()
      .withPhoneSignIn()
    

X (Твиттер)

Чтобы использовать вход через Twitter:

  1. Создайте учетные данные X API, следуя инструкциям на сайте для разработчиков X.

  2. В разделе Безопасность > Аутентификация > Способ входа консоли Firebase включите поставщика Twitter. Вам понадобятся ключ API и секретный код API из консоли разработчика X.

  3. Зарегистрируйте поставщика в экземпляре AuthService:

    let authService = AuthService()
      .withTwitterSignIn()
    

Стандартные поставщики OAuth2 и OIDC

FirebaseUI также поддерживает встроенных поставщиков OAuth, таких как GitHub, Microsoft и Yahoo, а также настраиваемых поставщиков OIDC, заданных в Firebase Authentication.

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

Если вы используете собственного поставщика OIDC, сначала настройте его в Firebase Authentication, а затем создайте OAuthProviderSwift с идентификатором поставщика и конфигурацией кнопки:

let lineProvider = OAuthProviderSwift(
  providerId: "oidc.line",
  buttonLabel: "Sign in with LINE",
  displayName: "LINE",
  iconSystemName: "person.crop.circle.badge.checkmark",
  buttonBackgroundColor: .green,
  buttonForegroundColor: .white
)

let authService = AuthService()
  .withOAuthSignIn(lineProvider)

Как использовать встроенное представление для аутентификации

FirebaseUI для SwiftUI предоставляет AuthPickerView – готовый интерфейс аутентификации, который обрабатывает весь процесс аутентификации. Это самый простой способ добавить аутентификацию в приложение.

Пример

Вот пример использования разметки типа AuthPickerView с несколькими поставщиками и вариантами конфигурации:

import FirebaseAppleSwiftUI
import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import SwiftUI

struct ContentView: View {
  let authService: AuthService

  init() {
    // Create configuration with options
    let configuration = AuthConfiguration(
      tosUrl: URL(string: "https://example.com/tos"),
      privacyPolicyUrl: URL(string: "https://example.com/privacy"),
      shouldAutoUpgradeAnonymousUsers: true
    )

    // Initialize AuthService with multiple providers
    authService = AuthService(configuration: configuration)
      .withEmailSignIn()
      .withAppleSignIn()
      .withGoogleSignIn()
  }

  var body: some View {
    AuthPickerView {
      authenticatedContent
    }
    .environment(authService)
  }

  var authenticatedContent: some View {
    NavigationStack {
      VStack(spacing: 20) {
        if authService.authenticationState == .authenticated {
          Text("Authenticated")
          
          Button("Manage Account") {
            authService.isPresented = true
          }
          .buttonStyle(.bordered)
          
          Button("Sign Out") {
            Task {
              try? await authService.signOut()
            }
          }
          .buttonStyle(.borderedProminent)
        } else {
          Text("Not Authenticated")
          
          Button("Sign In") {
            authService.isPresented = true
          }
          .buttonStyle(.borderedProminent)
        }
      }
      .navigationTitle("My App")
    }
    .onChange(of: authService.authenticationState) { _, newValue in
      // Automatically show auth UI when not authenticated
      if newValue != .authenticating {
        authService.isPresented = (newValue == .unauthenticated)
      }
    }
  }
}

Типичные настройки

Хотя все параметры AuthConfiguration являются необязательными, большинство приложений настраивают хотя бы следующие параметры:

let configuration = AuthConfiguration(
    logo: ImageResource.exampleLogoAsset,
    customStringsBundle: .main,
    tosUrl: URL(string: "https://example.com/tos"),
    privacyPolicyUrl: URL(string: "https://example.com/privacy"),
)
  • logo – изображение логотипа, которое показывается на странице аутентификации. Информацию о том, как добавить собственный объект изображения, можно найти в разделе Добавление изображений в проект Xcode.

  • customStringsBundle – использование специальных строк из указанного пакета. Этот параметр используется для локализации, а также для настройки строк по умолчанию, используемых AuthPickerView. Например, чтобы задать сообщение, которое будет показываться в верхней части листа аутентификации, создайте Localizable.strings со следующим содержимым:

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

Что входит в стандартное представление

При использовании AuthPickerView вы получаете:

  1. Sheet Presentation – интерфейс аутентификации отображается в виде модального листа.
  2. Встроенная навигация. Автоматический переход между экранами входа, восстановления пароля, многофакторной аутентификации, подтверждения по электронной почте и подтверждения по телефону.
  3. Управление состоянием аутентификации. Автоматическое переключение между интерфейсом аутентификации и вашим контентом на основе authService.authenticationState.
  4. Управление с помощью isPresented.authService.isPresented = true/false

Поведение, основанное на мнении

По умолчанию AuthPickerView содержит рекомендации по обработке нескольких сложных сценариев:

1. Как устранить конфликт аккаунтов

При возникновении конфликта аккаунтов (например, при входе с учетными данными, которые уже связаны с другим аккаунтом) AuthPickerView автоматически выполняет следующие действия:

  • Конфликты при анонимном обновлении. Если включен параметр shouldAutoUpgradeAnonymousUsers и при анонимном обновлении возникает конфликт, система автоматически выходит из аккаунта анонимного пользователя и входит в аккаунт с новыми учетными данными.
  • Другие конфликты. Если конфликт возникает между учетными данными неанонимных аккаунтов, система сохраняет ожидающие учетные данные и пытается связать их после успешного входа.

Это делается с помощью AccountConflictModifier, примененного на уровне NavigationStack.

2. Многофакторная аутентификация (МФА)

Если в вашей конфигурации включена многофакторная аутентификация:

  • Автоматически определяет, когда при входе требуется многофакторная аутентификация
  • Показывает подходящие экраны для многофакторной аутентификации (с помощью SMS или TOTP).
  • Обрабатывает процессы регистрации и управления многофакторной аутентификацией.
  • Поддерживает факторы на основе SMS и временных одноразовых кодов (TOTP)
3. Обработка ошибок

В представлениях по умолчанию предусмотрена встроенная обработка ошибок:

  • показывать понятные сообщения об ошибках в диалоговых окнах с предупреждениями;
  • Автоматически фильтрует ошибки, которые обрабатываются внутри системы (например, ошибки отмены, автоматически обработанные конфликты).
  • Использует локализованные сообщения об ошибках через StringUtils.
  • Ошибки передаются через ключ среды reportError

Если вход с помощью ссылки из электронного письма настроен:

  • Автоматически сохраняет адрес электронной почты в хранилище приложения.
  • Обрабатывает переход по ссылке на контент из электронного письма.
  • Управляет полным процессом подтверждения адреса электронной почты.
  • Поддерживает переход на платную версию анонимных пользователей по ссылке в электронном письме
5. Автоматическое преобразование анонимных пользователей

Если параметр shouldAutoUpgradeAnonymousUsers включен:

  • Автоматически пытается связать анонимные аккаунты с новыми учетными данными для входа.
  • Сохраняет пользовательские данные, обновляя анонимные сеансы, а не заменяя их.
  • Корректно обрабатывает конфликты при обновлении.
6. Повторная аутентификация в представлениях по умолчанию

Для выполнения действий, связанных с безопасностью, таких как удаление аккаунтов, изменение паролей или отмена регистрации факторов многофакторной аутентификации, требуется недавняя аутентификация. При использовании представлений по умолчанию повторная аутентификация выполняется автоматически на основе поставщика услуг входа пользователя.

Когда для выполнения операции, связанной с безопасностью, требуется повторная аутентификация, автоматически выполняются следующие действия:

  • Поставщики OAuth (Google, Apple, Facebook, Twitter и т. д.). Показать пользователю предупреждение с запросом подтверждения, а затем автоматически получить новые учетные данные и завершить операцию.

  • Электронная почта/пароль. Показывает лист, на котором пользователю предлагается ввести пароль, прежде чем продолжить.

  • Ссылка на электронную почту. Показывает оповещение с предложением отправить письмо с подтверждением, а затем лист с инструкциями по проверке электронной почты. Пользователь нажимает на ссылку в письме, чтобы повторно пройти аутентификацию.

  • Телефоны. Показывается предупреждение о необходимости подтверждения, а затем страница для ввода кода из SMS.

После успешной повторной аутентификации операция будет автоматически повторена. При использовании AuthPickerView или встроенных представлений для управления аккаунтом (UpdatePasswordView, SignedInView и т. д.) дополнительный код не требуется.

Расширенные возможности: создание собственных представлений для аутентификации

Если вам нужно больше контроля над интерфейсом или процессом навигации, вы можете создать собственные представления для аутентификации, используя AuthService для логики аутентификации.

Вы можете комбинировать элементы FirebaseUI с собственной логикой разными способами. В следующих разделах приведены примеры некоторых способов настройки.

Способ 1. Пользовательские кнопки с символом registerProvider()

Чтобы полностью контролировать внешний вид кнопки, вы можете создать собственную реализацию AuthProviderUI, которая будет оборачивать любого поставщика и возвращать ваше представление кнопки.

Как создать собственный интерфейс поставщика

Вот как создать специальную кнопку Twitter:

import FirebaseAuthSwiftUI
import FirebaseTwitterSwiftUI
import SwiftUI

// Step 1: Create your custom button view
struct CustomTwitterButton: View {
  let provider: TwitterProviderSwift
  @Environment(AuthService.self) private var authService
  @Environment(\.mfaHandler) private var mfaHandler

  var body: some View {
    Button {
      Task {
        do {
          let outcome = try await authService.signIn(provider)

          // Handle MFA if required
          if case let .mfaRequired(mfaInfo) = outcome,
             let onMFA = mfaHandler {
            onMFA(mfaInfo)
          }
        } catch {
          // Do Something Else
        }
      }
    } label: {
      HStack { // Your custom icon
        Text("Sign in with Twitter")
          .fontWeight(.semibold)
      }
      .frame(maxWidth: .infinity)
      .padding()
      .background(
        LinearGradient(
          colors: [Color.blue, Color.cyan],
          startPoint: .leading,
          endPoint: .trailing
        )
      )
      .foregroundColor(.white)
      .cornerRadius(12)
      .shadow(radius: 4)
    }
  }
}

// Step 2: Create a custom AuthProviderUI wrapper
class CustomTwitterProviderAuthUI: AuthProviderUI {
  private let typedProvider: TwitterProviderSwift
  var provider: AuthProviderSwift { typedProvider }
  let id: String = "twitter.com"

  init(provider: TwitterProviderSwift = TwitterProviderSwift()) {
    typedProvider = provider
  }

  @MainActor func authButton() -> AnyView {
    AnyView(CustomTwitterButton(provider: typedProvider))
  }
}

// Step 3: Use it in your app
struct ContentView: View {
  let authService: AuthService

  init() {
    let configuration = AuthConfiguration()
    authService = AuthService(configuration: configuration)

    // Register your custom provider UI
    authService.registerProvider(
      providerWithButton: CustomTwitterProviderAuthUI()
    )
    authService.isPresented = true
  }

  var body: some View {
    AuthPickerView {
      usersApp
    }
    .environment(authService)
  }

  var usersApp: some View {
    NavigationStack {
      VStack {
        Button {
          authService.isPresented = true
        } label: {
          Text("Authenticate")
        }
      }
    }
  }
}

Пример упрощенной кнопки

Вы также можете создать более простые пользовательские кнопки для любого поставщика:

import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import FirebaseAppleSwiftUI
import SwiftUI

// Custom Google Provider UI
class CustomGoogleProviderAuthUI: AuthProviderUI {
  private let typedProvider: GoogleProviderSwift
  var provider: AuthProviderSwift { typedProvider }
  let id: String = "google.com"
  
  init() {
    typedProvider = GoogleProviderSwift()
  }
  
  @MainActor func authButton() -> AnyView {
    AnyView(CustomGoogleButton(provider: typedProvider))
  }
}

struct CustomGoogleButton: View {
  let provider: GoogleProviderSwift
  @Environment(AuthService.self) private var authService
  
  var body: some View {
    Button {
      Task {
        try? await authService.signIn(provider)
      }
    } label: {
      HStack {
        Image(systemName: "g.circle.fill")
        Text("My Custom Google Button")
      }
      .frame(maxWidth: .infinity)
      .padding()
      .background(Color.purple) // Your custom color
      .foregroundColor(.white)
      .cornerRadius(10)
    }
  }
}

// Then use it
struct ContentView: View {
  let authService: AuthService
  
  init() {
    let configuration = AuthConfiguration()
    authService = AuthService(configuration: configuration)
      .withAppleSignIn() // Use default Apple button
    
    // Use custom Google button
    authService.registerProvider(
      providerWithButton: CustomGoogleProviderAuthUI()
    )
  }
  
  var body: some View {
    AuthPickerView {
      Text("App Content")
    }
    .environment(authService)
  }
}

Этот подход работает для всех поставщиков: Google, Apple, Twitter, Facebook, Phone и OAuth. Просто создайте собственное представление кнопки и оберните его в класс, соответствующий AuthProviderUI.

Способ 2. Кнопки по умолчанию с пользовательскими представлениями

Вы можете использовать AuthService.renderButtons() и пропустить AuthPickerView, чтобы отрисовать кнопки аутентификации по умолчанию, но при этом задать собственный макет и навигацию:

import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import FirebaseAppleSwiftUI
import SwiftUI

struct CustomAuthView: View {
  @Environment(AuthService.self) private var authService

  var body: some View {
    VStack(spacing: 30) {
      // Your custom logo/branding
      Image("app-logo")
        .resizable()
        .frame(width: 150, height: 150)
      
      Text("Welcome to My App")
        .font(.largeTitle)
        .fontWeight(.bold)
      
      Text("Sign in to continue")
        .font(.subheadline)
        .foregroundStyle(.secondary)
      
      // Render default auth buttons
      authService.renderButtons(spacing: 12)
        .padding()
    }
    .padding()
  }
}

struct ContentView: View {
  init() {
    let configuration = AuthConfiguration()
    
    authService = AuthService(configuration: configuration)
      .withGoogleSignIn()
      .withAppleSignIn()
  }
  
  let authService: AuthService

  var body: some View {
    NavigationStack {
      if authService.authenticationState == .authenticated {
        Text("Authenticated!")
      } else {
        CustomAuthView()
      }
    }
    .environment(authService)
  }
}

Способ 3. Пользовательские представления с пользовательской навигацией

Чтобы полностью контролировать процесс, вы можете пропустить AuthPickerView и создать собственную систему навигации:

import FirebaseAuth
import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import SwiftUI

enum CustomAuthRoute {
  case signIn
  case phoneVerification
  case mfaResolution
}

struct ContentView: View {
  private let authService: AuthService
  @State private var navigationPath: [CustomAuthRoute] = []
  @State private var errorMessage: String?

  init() {
    let configuration = AuthConfiguration()
    self.authService = AuthService(configuration: configuration)
      .withGoogleSignIn()
      .withPhoneSignIn()
  }

  var body: some View {
    NavigationStack(path: $navigationPath) {
      Group {
        if authService.authenticationState == .authenticated {
          authenticatedView
        } else {
          customSignInView
        }
      }
      .navigationDestination(for: CustomAuthRoute.self) { route in
        switch route {
        case .signIn:
          customSignInView
        case .phoneVerification:
          customPhoneVerificationView
        case .mfaResolution:
          customMFAView
        }
      }
    }
    .environment(authService)
    .alert("Error", isPresented: .constant(errorMessage != nil)) {
      Button("OK") {
        errorMessage = nil
      }
    } message: {
      Text(errorMessage ?? "")
    }
  }

  var customSignInView: some View {
    VStack(spacing: 20) {
      Text("Custom Sign In")
        .font(.title)
      
      Button("Sign in with Google") {
        Task {
          do {
            let provider = GoogleProviderSwift(clientID: Auth.auth().app?.options.clientID ?? "")
            let outcome = try await authService.signIn(provider)
            
            // Handle MFA if required
            if case .mfaRequired = outcome {
              navigationPath.append(.mfaResolution)
            }
          } catch {
            errorMessage = error.localizedDescription
          }
        }
      }
      .buttonStyle(.borderedProminent)
      
      Button("Phone Sign In") {
        navigationPath.append(.phoneVerification)
      }
      .buttonStyle(.bordered)
    }
    .padding()
  }

  var customPhoneVerificationView: some View {
    Text("Custom Phone Verification View")
    // Implement your custom phone auth UI here
  }

  var customMFAView: some View {
    Text("Custom MFA Resolution View")
    // Implement your custom MFA UI here
  }

  var authenticatedView: some View {
    VStack(spacing: 20) {
      Text("Welcome!")
      Text("Email: \(authService.currentUser?.email ?? "N/A")")
      
      Button("Sign Out") {
        Task {
          try? await authService.signOut()
        }
      }
      .buttonStyle(.borderedProminent)
    }
  }
}

Важные примечания о специальных представлениях

При создании собственных представлений вам нужно будет самостоятельно выполнять некоторые действия, которые AuthPickerView выполняет автоматически:

  1. Конфликты аккаунтов. Реализуйте собственную стратегию устранения конфликтов с помощью AuthServiceError.accountConflict.
  2. Обработка многофакторной аутентификации. Проверьте SignInOutcome на наличие .mfaRequired и вручную устраните проблему с многофакторной аутентификацией.
  3. Переход анонимного пользователя на более высокий уровень. Обрабатывает связывание анонимных аккаунтов, если включена функция shouldAutoUpgradeAnonymousUsers.
  4. Состояние навигации. Управление навигацией между разными экранами аутентификации (подтверждение по телефону, восстановление пароля и т. д.).
  5. Состояния загрузки. Показывайте индикаторы загрузки во время асинхронных операций аутентификации, отслеживая authService.authenticationState.
  6. Повторная аутентификация. Обрабатывайте ошибки повторной аутентификации для операций, связанных с безопасностью (см. раздел Повторная аутентификация в специальных представлениях ниже).

Повторная аутентификация в специальных представлениях

При создании собственных представлений обрабатывайте повторную аутентификацию, перехватывая определенные ошибки и реализуя собственный процесс. При выполнении конфиденциальных операций могут возникать четыре типа ошибок повторной аутентификации. Каждая из них содержит контекстную информацию.

Варианты реализации

Поставщики OAuth (Google, Apple, Facebook, Twitter и т. д.).

Перехватите ошибку и вызовите функцию reauthenticate(context:), которая автоматически обработает поток OAuth:

do {
  try await authService.deleteUser()
} catch let error as AuthServiceError {
  if case .oauthReauthenticationRequired(let context) = error {
    try await authService.reauthenticate(context: context)
    try await authService.deleteUser() // Retry operation
  }
}

Адрес электронной почты/пароль

Перехватите ошибку, запросите пароль, создайте учетные данные и вызовите reauthenticate(with:):

do {
  try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
  if case .emailReauthenticationRequired(let context) = error {
    // Show your password prompt UI
    let password = await promptUserForPassword()
    let credential = EmailAuthProvider.credential(
      withEmail: context.email,
      password: password
    )
    try await authService.reauthenticate(with: credential)
    try await authService.updatePassword(to: newPassword) // Retry
  }
}

Номер телефона

Обработайте ошибку, подтвердите номер телефона, создайте учетные данные и вызовите reauthenticate(with:):

do {
  try await authService.deleteUser()
} catch let error as AuthServiceError {
  if case .phoneReauthenticationRequired(let context) = error {
    // Send verification code
    let verificationId = try await authService.verifyPhoneNumber(
      phoneNumber: context.phoneNumber
    )
    // Show your SMS code input UI
    let code = await promptUserForSMSCode()
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId,
      verificationCode: code
    )
    try await authService.reauthenticate(with: credential)
    try await authService.deleteUser() // Retry
  }
}

Ссылка на электронную почту:

Обработайте ошибку, отправьте письмо с подтверждением и обработайте входящий URL:

do {
  try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
  if case .emailLinkReauthenticationRequired(let context) = error {
    // Send verification email
    try await authService.sendEmailSignInLink(
      email: context.email,
      isReauth: true
    )
    // Show your "Check your email" UI
    await showCheckEmailUI()
    // When user taps the link, it opens your app with a URL
    // Handle it in your URL handler:
    // try await authService.handleSignInLink(url: url)
    // The handleSignInLink method automatically completes reauthentication
    try await authService.updatePassword(to: newPassword) // Retry
  }
}

Все объекты контекста повторной аутентификации содержат свойство .displayMessage для текста, который видит пользователь.

Настраиваемые поставщики OAuth

Вы можете создать собственного поставщика OAuth для сервисов, не входящих в список встроенных:

⚠️ Важно! Чтобы использовать поставщиков OIDC (OpenID Connect), их необходимо настроить в разделе "Аутентификация" проекта Firebase. В консоли Firebase выберите Authentication (Аутентификация) → Sign-in method (Способ входа) и добавьте поставщика OIDC с необходимыми учетными данными (идентификатор клиента, секретный код клиента, URL издателя). В консоли разработчика поставщика также необходимо зарегистрировать URI перенаправления OAuth, предоставленный Firebase. Подробные инструкции по настройке приведены в документации по Firebase OIDC.

import FirebaseAuthSwiftUI
import FirebaseOAuthSwiftUI
import SwiftUI

struct ContentView: View {
  let authService: AuthService

  init() {
    let configuration = AuthConfiguration()
    
    authService = AuthService(configuration: configuration)
      .withOAuthSignIn(
        OAuthProviderSwift(
          providerId: "oidc.line",  // LINE OIDC provider
          scopes: ["profile", "openid", "email"],  // LINE requires these scopes
          displayName: "Sign in with LINE",
          buttonIcon: Image("line-logo"),
          buttonBackgroundColor: .green,
          buttonForegroundColor: .white
        )
      )
      .withOAuthSignIn(
        OAuthProviderSwift(
          providerId: "oidc.custom-provider",
          scopes: ["profile", "openid"],
          displayName: "Sign in with Custom",
          buttonIcon: Image(systemName: "person.circle"),
          buttonBackgroundColor: .purple,
          buttonForegroundColor: .white
        )
      )
  }

  var body: some View {
    AuthPickerView {
      Text("App Content")
    }
    .environment(authService)
  }
}