Добавьте функцию авторизации в ваше iOS-приложение с помощью FirebaseUI.

FirebaseUI for SwiftUI — это современная библиотека, ориентированная на SwiftUI и построенная на основе Firebase Authentication , которая предоставляет готовые сценарии входа в систему для вашего приложения.

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

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

Прежде чем начать

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

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

Установка

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

  1. В Xcode нажмите File > Add Package Dependencies .

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

    https://github.com/firebase/FirebaseUI-iOS
    
  3. В меню «Правило зависимости» выберите «До следующей основной версии» и установите минимальную версию на последнюю выпущенную версию .

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

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

    • FirebaseAuthSwiftUI

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

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

  6. Настройте AuthService FirebaseUI в инициализаторе вашего 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»:

  1. Настройка входа в систему с помощью Apple:

    1. Включите функцию «Вход с помощью Apple» для вашего приложения на странице «Сертификаты, идентификаторы и профили» на сайте разработчиков Apple.
    2. Свяжите свой веб-сайт с приложением, как описано в первом разделе раздела «Настройка входа в систему Apple для веб-браузера» . При появлении запроса зарегистрируйте следующий 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 откройте раздел «Подписание и возможности» в редакторе проекта и добавьте возможность «Вход с помощью Apple» .

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

    let authService = AuthService()
      .withAppleSignIn()
    

Google

Для использования входа через Google:

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

  2. Загрузите новую копию файла GoogleService-Info.plist вашего проекта и скопируйте её в свой проект Xcode. Замените существующие версии новой.

  3. Добавьте пользовательские схемы URL-адресов в свой проект Xcode:

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

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

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

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

    let authService = AuthService()
      .withGoogleSignIn()
    

Фейсбук

Для входа через Facebook:

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

  2. В разделе « Безопасность > Аутентификация > Метод входа» консоли Firebase включите поставщика Facebook . Вам понадобятся идентификатор приложения Facebook (App ID) и секретный ключ приложения (App Secret) с сайта Meta for Developers.

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

    let authService = AuthService()
     .withFacebookSignIn()
    

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

Для использования аутентификации по телефону:

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

  2. Настройте APN для вашего приложения в соответствии с инструкциями в разделе «Начать получать бесшумные уведомления» .

  3. Добавьте пользовательские схемы URL-адресов в свой проект Xcode:

    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 : использовать пользовательские строки из указанного Bundle. Это используется для локализации, а также для настройки строк по умолчанию, используемых AuthPickerView . Например, чтобы установить сообщение, отображаемое в верхней части окна аутентификации, создайте Localizable.strings со следующим содержимым:

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

Что входит в предварительно созданный вид?

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

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

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

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

1. Разрешение конфликтов учетных записей

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

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

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

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

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

  • Автоматически определяет, когда требуется многофакторная аутентификация при входе в систему.
  • Представляет соответствующие экраны разрешения многофакторной аутентификации (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. Обработка многофакторной аутентификации (MFA) : проверьте SignInOutcome на наличие .mfaRequired и выполните разрешение MFA вручную.
  3. Обновление анонимных пользователей : Обработка привязки анонимных учетных записей, если включен параметр shouldAutoUpgradeAnonymousUsers
  4. Состояние навигации : Управление навигацией между различными экранами аутентификации (подтверждение по телефону, восстановление пароля и т. д.).
  5. Состояния загрузки : Отображение индикаторов загрузки во время асинхронных операций аутентификации путем отслеживания authService.authenticationState
  6. Повторная аутентификация : Обработка ошибок повторной аутентификации для конфиденциальных операций (см. раздел «Повторная аутентификация в пользовательских представлениях » ниже).

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

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

шаблоны реализации

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

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

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

Электронная почта/Пароль:

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

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

Телефон:

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

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

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

Перехватите ошибку, отправьте письмо с подтверждением и обработайте входящий 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 перейдите в раздел «Аутентификация» → «Метод входа» и добавьте свой 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)
  }
}