From 3b81ccb671f3b9dd28f645a99f1c18e1c0f0a82d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 13:27:37 +0000 Subject: [PATCH 01/20] docs: first draft --- FirebaseSwiftUI/first_draft.md | 1576 ++++++++++++++++++++++++++++++++ 1 file changed, 1576 insertions(+) create mode 100644 FirebaseSwiftUI/first_draft.md diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md new file mode 100644 index 0000000000..0ece2a5f9d --- /dev/null +++ b/FirebaseSwiftUI/first_draft.md @@ -0,0 +1,1576 @@ +# FirebaseUI for SwiftUI Documentation + +## Table of Contents + +1. [Installation](#installation) +2. [Getting Started](#getting-started) +3. [Usage with Default Views](#usage-with-default-views) +4. [Usage with Custom Views](#usage-with-custom-views) +5. [API Reference](#api-reference) + +--- + +## Installation + +### Using Swift Package Manager + +1. Launch Xcode and open the project or workspace where you want to add FirebaseUI for SwiftUI. +2. In the menu bar, go to: **File > Add Package Dependencies...** +3. Enter the Package URL: `https://github.com/firebase/FirebaseUI-iOS` +4. In the **Dependency Rule** dropdown, select **Exact Version** and set the version to `15.1.0-alpha` in the resulting text input. +5. Select the targets you wish to add to your app. The available SwiftUI packages are: + - **FirebaseAuthSwiftUI** (required - includes Email auth provider) + - **FirebaseAppleSwiftUI** (Sign in with Apple) + - **FirebaseGoogleSwiftUI** (Sign in with Google) + - **FirebaseFacebookSwiftUI** (Sign in with Facebook) + - **FirebasePhoneAuthSwiftUI** (Phone authentication) + - **FirebaseTwitterSwiftUI** (Sign in with Twitter) + - **FirebaseOAuthSwiftUI** (Generic OAuth providers like GitHub, Microsoft, Yahoo) +6. Press the **Add Packages** button to complete installation. + +### Platform Requirements + +- **Minimum iOS Version**: iOS 17+ +- **Swift Version**: Swift 6.0+ + +--- + +## Getting Started + +### Basic Setup + +Before using FirebaseUI for SwiftUI, you need to configure Firebase in your app: + +1. Follow steps 2, 3 & 5 in [adding Firebase to your iOS app](https://firebase.google.com/docs/ios/setup). +2. Update your app entry point: + +```swift +import FirebaseAuthSwiftUI +import FirebaseCore +import SwiftUI + +class AppDelegate: NSObject, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + FirebaseApp.configure() + return true + } +} + +@main +struct YourApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + +--- + +## Usage with Default Views + +FirebaseUI for SwiftUI provides `AuthPickerView`, a pre-built, opinionated authentication UI that handles the entire authentication flow for you. This is the easiest way to add authentication to your app. + +### Minimal Example + +Here's a minimal example using the default views with email authentication: + +```swift +import FirebaseAuthSwiftUI +import SwiftUI + +struct ContentView: View { + let authService: AuthService + + init() { + let configuration = AuthConfiguration() + + authService = AuthService(configuration: configuration) + .withEmailSignIn() + } + + var body: some View { + AuthPickerView { + // Your authenticated app content goes here + Text("Welcome to your app!") + } + .environment(authService) + } +} +``` + +### Full-Featured Example + +Here's a more complete example with multiple providers and configuration options: + +```swift +import FirebaseAppleSwiftUI +import FirebaseAuthSwiftUI +import FirebaseFacebookSwiftUI +import FirebaseGoogleSwiftUI +import FirebaseOAuthSwiftUI +import FirebasePhoneAuthSwiftUI +import FirebaseTwitterSwiftUI +import SwiftUI + +struct ContentView: View { + let authService: AuthService + + init() { + // Configure email link sign-in + let actionCodeSettings = ActionCodeSettings() + actionCodeSettings.handleCodeInApp = true + actionCodeSettings.url = URL(string: "https://yourapp.firebaseapp.com") + actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!) + + // Create configuration with options + let configuration = AuthConfiguration( + shouldAutoUpgradeAnonymousUsers: true, + tosUrl: URL(string: "https://example.com/tos"), + privacyPolicyUrl: URL(string: "https://example.com/privacy"), + emailLinkSignInActionCodeSettings: actionCodeSettings, + mfaEnabled: true + ) + + // Initialize AuthService with multiple providers + authService = AuthService(configuration: configuration) + .withEmailSignIn() + .withAppleSignIn() + .withGoogleSignIn() + .withFacebookSignIn(FacebookProviderSwift()) + .withPhoneSignIn() + .withTwitterSignIn() + .withOAuthSignIn(OAuthProviderSwift.github()) + .withOAuthSignIn(OAuthProviderSwift.microsoft()) + .withOAuthSignIn(OAuthProviderSwift.yahoo()) + } + + 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) + } + } + } +} +``` + +### How Default Views Work + +When you use `AuthPickerView`, you get: + +1. **Sheet Presentation**: Authentication UI appears as a modal sheet +2. **Built-in Navigation**: Automatic navigation between sign-in, password recovery, MFA, email link, and phone verification screens +3. **Authentication State Management**: Automatically switches between auth UI and your content based on `authService.authenticationState` +4. **Control via `isPresented`**: Control when the auth sheet appears by setting `authService.isPresented = true/false` + +### Opinionated Behaviors in Default Views + +The default `AuthPickerView` handles several complex scenarios automatically: + +#### 1. **Account Conflict Resolution** + +When an account conflict occurs (e.g., signing in with a credential that's already linked to another account), `AuthPickerView` automatically handles it: + +- **Anonymous Upgrade Conflicts**: If `shouldAutoUpgradeAnonymousUsers` is enabled and a conflict occurs during anonymous upgrade, the system automatically signs out the anonymous user and signs in with the new credential. +- **Other Conflicts**: For credential conflicts between non-anonymous accounts, the system stores the pending credential and attempts to link it after successful sign-in. + +This is handled by the `AccountConflictModifier` applied at the NavigationStack level. + +#### 2. **Multi-Factor Authentication (MFA)** + +When MFA is enabled in your configuration: + +- Automatically detects when MFA is required during sign-in +- Presents appropriate MFA resolution screens (SMS or TOTP) +- Handles MFA enrollment and management flows +- Supports both SMS-based and Time-based One-Time Password (TOTP) factors + +#### 3. **Error Handling** + +The default views include built-in error handling: + +- Displays user-friendly error messages in alert dialogs +- Automatically filters out errors that are handled internally (e.g., cancellation errors, auto-handled conflicts) +- Uses localized error messages via `StringUtils` +- Errors are propagated through the `reportError` environment key + +#### 4. **Email Link Sign-In** + +When email link sign-in is configured: + +- Automatically stores the email address in app storage +- Handles deep link navigation from email +- Manages the complete email verification flow +- Supports anonymous user upgrades via email link + +#### 5. **Anonymous User Auto-Upgrade** + +When `shouldAutoUpgradeAnonymousUsers` is enabled: + +- Automatically attempts to link anonymous accounts with new sign-in credentials +- Preserves user data by upgrading instead of replacing anonymous sessions +- Handles upgrade conflicts gracefully + +### Available Auth Methods in Default Views + +The default views support: + +- **Email/Password Authentication** (built into `FirebaseAuthSwiftUI`) +- **Email Link Authentication** (passwordless) +- **Phone Authentication** (SMS verification) +- **Sign in with Apple** +- **Sign in with Google** +- **Sign in with Facebook** (Classic and Limited Login) +- **Sign in with Twitter** +- **Generic OAuth Providers** (GitHub, Microsoft, Yahoo, or custom OIDC) + +--- + +## Usage with Custom Views + +If you need more control over the UI or navigation flow, you can build your own custom authentication views while still leveraging the `AuthService` for authentication logic. + +### Approach 1: Custom Buttons with Default Navigation + +You can use `AuthService.renderButtons()` to render the default authentication buttons while providing your own layout and navigation: + +```swift +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 CustomAuthView: View { + @Environment(AuthService.self) private var authService + @State private var showEmailView = false + + var body: some View { + VStack(spacing: 20) { + // Your custom logo/branding + Image("app-logo") + .resizable() + .frame(width: 150, height: 150) + + Text("Welcome to My App") + .font(.largeTitle) + .fontWeight(.bold) + + // Custom email button + Button("Sign in with Email") { + showEmailView = true + } + .buttonStyle(.bordered) + + // Render other default auth buttons (Google, Apple, etc.) + authService.renderButtons(spacing: 12) + .padding() + } + .padding() + .sheet(isPresented: $showEmailView) { + SimpleEmailView() + .environment(authService) + } + } +} + +// Simple custom email view +struct SimpleEmailView: View { + @Environment(AuthService.self) private var authService + @State private var email = "" + + var body: some View { + VStack(spacing: 20) { + Text("Enter Your Email") + .font(.title) + + TextField("Email", text: $email) + .textFieldStyle(.roundedBorder) + .autocapitalization(.none) + .keyboardType(.emailAddress) + + Button("Continue") { + // Handle email submission + print("Email entered: \(email)") + } + .buttonStyle(.borderedProminent) + } + .padding() + } +} + +struct ContentView: View { + let authService: AuthService + + init() { + let configuration = AuthConfiguration() + + authService = AuthService(configuration: configuration) + .withGoogleSignIn() + .withAppleSignIn() + } + + var body: some View { + NavigationStack { + if authService.authenticationState == .authenticated { + Text("Authenticated!") + } else { + CustomAuthView() + } + } + .environment(authService) + } +} +``` + +### Approach 2: Custom Buttons with `registerProvider()` + +For complete control over button appearance, you can create your own custom `AuthProviderUI` implementation that wraps any provider and returns your custom button view. + +#### Creating a Custom Provider UI + +Here's how to create a custom Twitter button as an example: + +```swift +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 { + Image("twitter-logo") // 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() + ) + } + + var body: some View { + AuthPickerView { + Text("Welcome!") + } + .environment(authService) + } +} +``` + +#### Simplified Custom Button Example + +You can also create simpler custom buttons for any provider: + +```swift +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) + } +} +``` + +This approach works for all providers: Google, Apple, Twitter, Facebook, Phone, and OAuth providers. Simply create your custom button view and wrap it in a class conforming to `AuthProviderUI`. + +### Approach 3: Custom Views with Custom Navigation + +For complete control over the entire flow, you can bypass `AuthPickerView` and build your own navigation system: + +```swift +import FirebaseAuthSwiftUI +import FirebaseGoogleSwiftUI +import SwiftUI + +enum CustomAuthRoute { + case signIn + case phoneVerification + case mfaResolution +} + +struct ContentView: View { + @State private var authService: AuthService + @State private var navigationPath: [CustomAuthRoute] = [] + @State private var errorMessage: String? + + init() { + let configuration = AuthConfiguration() + let service = AuthService(configuration: configuration) + .withGoogleSignIn() + .withPhoneSignIn() + + _authService = State(initialValue: service) + } + + 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() + 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) + } + } +} +``` + +### Important Considerations for Custom Views + +When building custom views, you need to handle several things yourself that `AuthPickerView` handles automatically: + +1. **Account Conflicts**: Implement your own conflict resolution strategy using `AuthServiceError.accountConflict` +2. **MFA Handling**: Check `SignInOutcome` for `.mfaRequired` and handle MFA resolution manually +3. **Anonymous User Upgrades**: Handle the linking of anonymous accounts if `shouldAutoUpgradeAnonymousUsers` is enabled +4. **Error Display**: Catch and display errors from `AuthService` methods appropriately +5. **Navigation State**: Manage navigation between different auth screens (phone verification, password recovery, etc.) +6. **Loading States**: Show loading indicators during async authentication operations by observing `authService.authenticationState` + +### Custom OAuth Providers + +You can create custom OAuth providers for services beyond the built-in ones: + +```swift +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 + displayName: "Sign in with LINE", + buttonIcon: Image("line-logo"), + buttonBackgroundColor: .green, + buttonForegroundColor: .white + ) + ) + .withOAuthSignIn( + OAuthProviderSwift( + providerId: "oidc.custom-provider", + displayName: "Sign in with Custom", + buttonIcon: Image(systemName: "person.circle"), + buttonBackgroundColor: .purple, + buttonForegroundColor: .white + ) + ) + } + + var body: some View { + AuthPickerView { + Text("App Content") + } + .environment(authService) + } +} +``` + +--- + +## API Reference + +### AuthConfiguration + +The `AuthConfiguration` struct allows you to customize the behavior of the authentication flow. + +```swift +public struct AuthConfiguration { + public init( + logo: ImageResource? = nil, + languageCode: String? = nil, + shouldHideCancelButton: Bool = false, + interactiveDismissEnabled: Bool = true, + shouldAutoUpgradeAnonymousUsers: Bool = false, + customStringsBundle: Bundle? = nil, + tosUrl: URL? = nil, + privacyPolicyUrl: URL? = nil, + emailLinkSignInActionCodeSettings: ActionCodeSettings? = nil, + verifyEmailActionCodeSettings: ActionCodeSettings? = nil, + mfaEnabled: Bool = false, + allowedSecondFactors: Set = [.sms, .totp], + mfaIssuer: String = "Firebase Auth" + ) +} +``` + +#### Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `logo` | `ImageResource?` | `nil` | Custom logo to display in the authentication UI. If not provided, the default Firebase logo is used. | +| `languageCode` | `String?` | `nil` | Language code for localized strings (e.g., "en", "es", "fr"). If not provided, uses system language. | +| `shouldHideCancelButton` | `Bool` | `false` | When `true`, hides the cancel button in auth flows, preventing users from dismissing the UI. Useful for mandatory authentication. | +| `interactiveDismissEnabled` | `Bool` | `true` | When `false`, prevents users from dismissing auth sheets by swiping down. | +| `shouldAutoUpgradeAnonymousUsers` | `Bool` | `false` | When `true`, automatically links anonymous user accounts with new sign-in credentials, preserving any data associated with the anonymous session. | +| `customStringsBundle` | `Bundle?` | `nil` | Custom bundle for string localizations. Allows you to override default strings with your own translations. | +| `tosUrl` | `URL?` | `nil` | URL to your Terms of Service. When both `tosUrl` and `privacyPolicyUrl` are set, links are displayed in the auth UI. | +| `privacyPolicyUrl` | `URL?` | `nil` | URL to your Privacy Policy. When both `tosUrl` and `privacyPolicyUrl` are set, links are displayed in the auth UI. | +| `emailLinkSignInActionCodeSettings` | `ActionCodeSettings?` | `nil` | Configuration for email link (passwordless) sign-in. Must be set to use email link authentication. | +| `verifyEmailActionCodeSettings` | `ActionCodeSettings?` | `nil` | Configuration for email verification. Used when sending verification emails to users. | +| `mfaEnabled` | `Bool` | `false` | Enables Multi-Factor Authentication support. When enabled, users can enroll in and use MFA. | +| `allowedSecondFactors` | `Set` | `[.sms, .totp]` | Set of allowed MFA factor types. Options are `.sms` (phone-based) and `.totp` (authenticator app). | +| `mfaIssuer` | `String` | `"Firebase Auth"` | The issuer name displayed in TOTP authenticator apps when users enroll. | + +#### Notes + +- Both `tosUrl` and `privacyPolicyUrl` must be set for the links to appear in the UI. +- `emailLinkSignInActionCodeSettings` is **required** if you want to use email link sign-in. The `ActionCodeSettings` must have: + - `handleCodeInApp = true` + - A valid `url` + - iOS bundle ID configured via `setIOSBundleID()` + +--- + +### AuthService + +The main service class that manages authentication state and operations. + +#### Initialization + +```swift +public init( + configuration: AuthConfiguration = AuthConfiguration(), + auth: Auth = Auth.auth() +) +``` + +Creates a new `AuthService` instance. + +**Parameters:** +- `configuration`: Configuration for auth behavior (default: `AuthConfiguration()`) +- `auth`: Firebase Auth instance to use (default: `Auth.auth()`) + +--- + +#### Configuring Providers + +##### Email Authentication + +```swift +public func withEmailSignIn() -> AuthService +``` + +Enables email/password and email link authentication. Uses default behavior (navigates to email link view when tapped). + +```swift +public func withEmailSignIn(onTap: @escaping @MainActor () -> Void) -> AuthService +``` + +Enables email authentication with a custom callback when the email button is tapped. + +**Example:** + +```swift +authService + .withEmailSignIn() +``` + +--- + +##### Phone Authentication + +```swift +// Available when importing FirebasePhoneAuthSwiftUI +public func withPhoneSignIn() -> AuthService +``` + +Enables phone number authentication with SMS verification. + +**Example:** + +```swift +authService + .withPhoneSignIn() +``` + +--- + +##### Sign in with Apple + +```swift +// Available when importing FirebaseAppleSwiftUI +public func withAppleSignIn() -> AuthService +``` + +Enables Sign in with Apple authentication. + +**Example:** + +```swift +authService + .withAppleSignIn() +``` + +--- + +##### Sign in with Google + +```swift +// Available when importing FirebaseGoogleSwiftUI +public func withGoogleSignIn() -> AuthService +``` + +Enables Sign in with Google authentication. + +**Example:** + +```swift +authService + .withGoogleSignIn() +``` + +--- + +##### Sign in with Facebook + +```swift +// Available when importing FirebaseFacebookSwiftUI +public func withFacebookSignIn(_ provider: FacebookProviderSwift) -> AuthService +``` + +Enables Sign in with Facebook authentication. + +**Parameters:** +- `provider`: An instance of `FacebookProviderSwift()` for classic login or `FacebookProviderSwift(useClassicLogin: false)` for limited login. + +**Example:** + +```swift +authService + .withFacebookSignIn(FacebookProviderSwift()) +``` + +--- + +##### Sign in with Twitter + +```swift +// Available when importing FirebaseTwitterSwiftUI +public func withTwitterSignIn() -> AuthService +``` + +Enables Sign in with Twitter authentication. + +**Example:** + +```swift +authService + .withTwitterSignIn() +``` + +--- + +##### Generic OAuth Providers + +```swift +// Available when importing FirebaseOAuthSwiftUI +public func withOAuthSignIn(_ provider: OAuthProviderSwift) -> AuthService +``` + +Enables authentication with generic OAuth/OIDC providers. + +**Built-in Providers:** +- `OAuthProviderSwift.github()` +- `OAuthProviderSwift.microsoft()` +- `OAuthProviderSwift.yahoo()` + +**Custom Provider:** + +```swift +OAuthProviderSwift( + providerId: String, + displayName: String, + buttonIcon: Image, + buttonBackgroundColor: Color, + buttonForegroundColor: Color +) +``` + +**Example:** + +```swift +authService + .withOAuthSignIn(OAuthProviderSwift.github()) + .withOAuthSignIn(OAuthProviderSwift.microsoft()) + .withOAuthSignIn( + OAuthProviderSwift( + providerId: "oidc.custom-provider", + displayName: "Sign in with Custom", + buttonIcon: Image("custom-logo"), + buttonBackgroundColor: .blue, + buttonForegroundColor: .white + ) + ) +``` + +--- + +#### Custom Provider Registration + +```swift +public func registerProvider(providerWithButton: AuthProviderUI) +``` + +Registers a custom authentication provider that conforms to `AuthProviderUI`. + +**Parameters:** +- `providerWithButton`: A custom provider implementing the `AuthProviderUI` protocol. + +**Example:** + +```swift +let customProvider = MyCustomProvider() +authService.registerProvider(providerWithButton: customProvider) +``` + +--- + +#### Rendering Authentication Buttons + +```swift +public func renderButtons(spacing: CGFloat = 16) -> AnyView +``` + +Renders all registered authentication provider buttons as a vertical stack. + +**Parameters:** +- `spacing`: Vertical spacing between buttons (default: 16) + +**Returns:** An `AnyView` containing all auth buttons. + +**Example:** + +```swift +VStack { + Text("Choose a sign-in method") + authService.renderButtons(spacing: 12) +} +``` + +--- + +#### Authentication Operations + +##### Sign In with Credential + +```swift +public func signIn(_ provider: CredentialAuthProviderSwift) async throws -> SignInOutcome +``` + +Signs in using a provider that conforms to `CredentialAuthProviderSwift`. + +**Parameters:** +- `provider`: The authentication provider to use. + +**Returns:** `SignInOutcome` - either `.signedIn(AuthDataResult?)` or `.mfaRequired(MFARequired)` + +**Throws:** `AuthServiceError` or Firebase Auth errors + +**Example:** + +```swift +Task { + do { + let outcome = try await authService.signIn(GoogleProviderSwift()) + switch outcome { + case .signedIn(let result): + print("Signed in: \(result?.user.email ?? "")") + case .mfaRequired(let mfaInfo): + // Handle MFA resolution + print("MFA required: \(mfaInfo)") + } + } catch { + print("Sign in error: \(error)") + } +} +``` + +--- + +##### Sign In with Email/Password + +```swift +public func signIn(email: String, password: String) async throws -> SignInOutcome +``` + +Signs in using email and password credentials. + +**Parameters:** +- `email`: User's email address +- `password`: User's password + +**Returns:** `SignInOutcome` + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +##### Create User with Email/Password + +```swift +public func createUser(email: String, password: String) async throws -> SignInOutcome +``` + +Creates a new user account with email and password. + +**Parameters:** +- `email`: New user's email address +- `password`: New user's password + +**Returns:** `SignInOutcome` + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +##### Sign Out + +```swift +public func signOut() async throws +``` + +Signs out the current user. + +**Throws:** Firebase Auth errors + +**Example:** + +```swift +Button("Sign Out") { + Task { + try await authService.signOut() + } +} +``` + +--- + +##### Link Accounts + +```swift +public func linkAccounts(credentials: AuthCredential) async throws +``` + +Links a new authentication method to the current user's account. + +**Parameters:** +- `credentials`: The credential to link + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +#### Email Link (Passwordless) Authentication + +##### Send Email Sign-In Link + +```swift +public func sendEmailSignInLink(email: String) async throws +``` + +Sends a sign-in link to the specified email address. + +**Parameters:** +- `email`: Email address to send the link to + +**Throws:** `AuthServiceError` or Firebase Auth errors + +**Requirements:** `emailLinkSignInActionCodeSettings` must be configured in `AuthConfiguration` + +--- + +##### Handle Sign-In Link + +```swift +public func handleSignInLink(url: URL) async throws +``` + +Handles the sign-in flow when the user taps the email link. + +**Parameters:** +- `url`: The deep link URL from the email + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +#### Phone Authentication + +##### Verify Phone Number + +```swift +public func verifyPhoneNumber(phoneNumber: String) async throws -> String +``` + +Sends a verification code to the specified phone number. + +**Parameters:** +- `phoneNumber`: Phone number in E.164 format (e.g., "+15551234567") + +**Returns:** Verification ID to use when verifying the code + +**Throws:** Firebase Auth errors + +--- + +##### Sign In with Phone Number + +```swift +public func signInWithPhoneNumber( + verificationID: String, + verificationCode: String +) async throws +``` + +Signs in using a phone number and verification code. + +**Parameters:** +- `verificationID`: The verification ID returned from `verifyPhoneNumber()` +- `verificationCode`: The SMS code received by the user + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +#### User Profile Management + +##### Update Display Name + +```swift +public func updateUserDisplayName(name: String) async throws +``` + +Updates the current user's display name. + +**Parameters:** +- `name`: New display name + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Update Photo URL + +```swift +public func updateUserPhotoURL(url: URL) async throws +``` + +Updates the current user's photo URL. + +**Parameters:** +- `url`: URL to the user's profile photo + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Update Password + +```swift +public func updatePassword(to password: String) async throws +``` + +Updates the current user's password. May require recent authentication. + +**Parameters:** +- `password`: New password + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Send Email Verification + +```swift +public func sendEmailVerification() async throws +``` + +Sends a verification email to the current user's email address. + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Delete User + +```swift +public func deleteUser() async throws +``` + +Deletes the current user's account. May require recent authentication. + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +#### Multi-Factor Authentication (MFA) + +##### Start MFA Enrollment + +```swift +public func startMfaEnrollment( + type: SecondFactorType, + accountName: String? = nil, + issuer: String? = nil +) async throws -> EnrollmentSession +``` + +Initiates enrollment for a second factor. + +**Parameters:** +- `type`: Type of second factor (`.sms` or `.totp`) +- `accountName`: Account name for TOTP (defaults to user's email) +- `issuer`: Issuer name for TOTP (defaults to `configuration.mfaIssuer`) + +**Returns:** `EnrollmentSession` containing enrollment information + +**Throws:** `AuthServiceError` if MFA is not enabled or factor type not allowed + +**Requirements:** `mfaEnabled` must be `true` in `AuthConfiguration` + +--- + +##### Send SMS Verification for Enrollment + +```swift +public func sendSmsVerificationForEnrollment( + session: EnrollmentSession, + phoneNumber: String +) async throws -> String +``` + +Sends SMS verification code during MFA enrollment (for SMS-based second factor). + +**Parameters:** +- `session`: The enrollment session from `startMfaEnrollment()` +- `phoneNumber`: Phone number to enroll (E.164 format) + +**Returns:** Verification ID for completing enrollment + +**Throws:** `AuthServiceError` + +--- + +##### Complete MFA Enrollment + +```swift +public func completeEnrollment( + session: EnrollmentSession, + verificationId: String?, + verificationCode: String, + displayName: String +) async throws +``` + +Completes the MFA enrollment process. + +**Parameters:** +- `session`: The enrollment session +- `verificationId`: Verification ID (required for SMS, ignored for TOTP) +- `verificationCode`: The verification code from SMS or TOTP app +- `displayName`: Display name for this MFA factor + +**Throws:** `AuthServiceError` + +--- + +##### Unenroll MFA Factor + +```swift +public func unenrollMFA(_ factorUid: String) async throws -> [MultiFactorInfo] +``` + +Removes an MFA factor from the user's account. + +**Parameters:** +- `factorUid`: UID of the factor to remove + +**Returns:** Updated list of remaining enrolled factors + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Resolve MFA Challenge (SMS) + +```swift +public func resolveSmsChallenge(hintIndex: Int) async throws -> String +``` + +Sends SMS code for resolving an MFA challenge during sign-in. + +**Parameters:** +- `hintIndex`: Index of the MFA hint to use + +**Returns:** Verification ID for completing sign-in + +**Throws:** `AuthServiceError` + +--- + +##### Resolve Sign-In with MFA + +```swift +public func resolveSignIn( + code: String, + hintIndex: Int, + verificationId: String? = nil +) async throws +``` + +Completes sign-in by verifying the MFA code. + +**Parameters:** +- `code`: The MFA code from SMS or TOTP app +- `hintIndex`: Index of the MFA hint being used +- `verificationId`: Verification ID (required for SMS, ignored for TOTP) + +**Throws:** `AuthServiceError` + +--- + +#### Public Properties + +```swift +public let configuration: AuthConfiguration +``` +The configuration used by this service. + +--- + +```swift +public let auth: Auth +``` +The Firebase Auth instance. + +--- + +```swift +public var isPresented: Bool +``` +Controls whether the authentication sheet is presented (when using `AuthPickerView`). + +--- + +```swift +public var currentUser: User? +``` +The currently signed-in Firebase user, or `nil` if not authenticated. + +--- + +```swift +public var authenticationState: AuthenticationState +``` +Current authentication state: `.unauthenticated`, `.authenticating`, or `.authenticated`. + +--- + +```swift +public var authenticationFlow: AuthenticationFlow +``` +Current flow type: `.signIn` or `.signUp`. + +--- + +```swift +public private(set) var navigator: Navigator +``` +Navigator for managing navigation routes in default views. + +--- + +```swift +public var authView: AuthView? +``` +Currently displayed auth view (e.g., `.emailLink`, `.mfaResolution`). + +--- + +### AuthPickerView + +A pre-built view that provides complete authentication UI. + +```swift +public struct AuthPickerView: View { + public init(@ViewBuilder content: @escaping () -> Content = { EmptyView() }) +} +``` + +**Parameters:** +- `content`: Your app's authenticated content, shown when user is signed in. + +**Usage:** + +```swift +AuthPickerView { + // Your app content here + Text("Welcome!") +} +.environment(authService) +``` + +**Behavior:** +- Presents authentication UI as a modal sheet controlled by `authService.isPresented` +- Automatically handles navigation between auth screens +- Includes built-in error handling and account conflict resolution +- Supports MFA flows automatically + +--- + +### Enums and Types + +#### AuthenticationState + +```swift +public enum AuthenticationState { + case unauthenticated + case authenticating + case authenticated +} +``` + +Represents the current authentication state. + +--- + +#### SignInOutcome + +```swift +public enum SignInOutcome { + case mfaRequired(MFARequired) + case signedIn(AuthDataResult?) +} +``` + +Result of a sign-in attempt. Either successful or requiring MFA. + +--- + +#### SecondFactorType + +```swift +public enum SecondFactorType { + case sms + case totp +} +``` + +Types of second factors for MFA. + +--- + +#### AuthServiceError + +```swift +public enum AuthServiceError: Error { + case noCurrentUser + case notConfiguredActionCodeSettings(String) + case invalidEmailLink(String) + case providerNotFound(String) + case invalidCredentials(String) + case multiFactorAuth(String) + case reauthenticationRequired(String) + case accountConflict(AccountConflictContext) +} +``` + +Errors specific to `AuthService` operations. + +--- + +### Best Practices + +1. **Initialize AuthService in the parent view**: Create `AuthService` once and pass it down via the environment. + +2. **Handle MFA outcomes**: Always check for `.mfaRequired` when calling sign-in methods if MFA is enabled. + +3. **Use ActionCodeSettings for email link**: Email link sign-in requires proper `ActionCodeSettings` configuration. + +4. **Test with anonymous users**: If using `shouldAutoUpgradeAnonymousUsers`, test the upgrade flow thoroughly. + +5. **Observe authentication state**: Use `onChange(of: authService.authenticationState)` to react to authentication changes. + +6. **Error handling in custom views**: When not using `AuthPickerView`, implement comprehensive error handling. + +7. **Provider-specific setup**: Some providers (Google, Facebook) require additional configuration in AppDelegate or Info.plist. See the [sample app](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui) for examples. + +--- + +## Additional Resources + +- [Sample SwiftUI App](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui/FirebaseSwiftUIExample) +- [Firebase iOS Setup Guide](https://firebase.google.com/docs/ios/setup) +- [Firebase Authentication Documentation](https://firebase.google.com/docs/auth) +- [FirebaseUI-iOS GitHub Repository](https://github.com/firebase/FirebaseUI-iOS) + +--- + +## Feedback + +This is an alpha release. Please file feedback and issues in the [repository's issue tracker](https://github.com/firebase/FirebaseUI-iOS/issues). + From b063b8409a81dc01f1a5f5365e839b651e92d68c Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:10:47 +0000 Subject: [PATCH 02/20] chore: fix first approach --- FirebaseSwiftUI/first_draft.md | 64 ++-------------------------------- 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md index 0ece2a5f9d..c30590f49d 100644 --- a/FirebaseSwiftUI/first_draft.md +++ b/FirebaseSwiftUI/first_draft.md @@ -272,7 +272,7 @@ The default views support: If you need more control over the UI or navigation flow, you can build your own custom authentication views while still leveraging the `AuthService` for authentication logic. -### Approach 1: Custom Buttons with Default Navigation +### Approach 1: Default Buttons with Custom Views You can use `AuthService.renderButtons()` to render the default authentication buttons while providing your own layout and navigation: @@ -308,67 +308,7 @@ struct CustomAuthView: View { } } -struct CustomAuthView: View { - @Environment(AuthService.self) private var authService - @State private var showEmailView = false - - var body: some View { - VStack(spacing: 20) { - // Your custom logo/branding - Image("app-logo") - .resizable() - .frame(width: 150, height: 150) - - Text("Welcome to My App") - .font(.largeTitle) - .fontWeight(.bold) - - // Custom email button - Button("Sign in with Email") { - showEmailView = true - } - .buttonStyle(.bordered) - - // Render other default auth buttons (Google, Apple, etc.) - authService.renderButtons(spacing: 12) - .padding() - } - .padding() - .sheet(isPresented: $showEmailView) { - SimpleEmailView() - .environment(authService) - } - } -} - -// Simple custom email view -struct SimpleEmailView: View { - @Environment(AuthService.self) private var authService - @State private var email = "" - - var body: some View { - VStack(spacing: 20) { - Text("Enter Your Email") - .font(.title) - - TextField("Email", text: $email) - .textFieldStyle(.roundedBorder) - .autocapitalization(.none) - .keyboardType(.emailAddress) - - Button("Continue") { - // Handle email submission - print("Email entered: \(email)") - } - .buttonStyle(.borderedProminent) - } - .padding() - } -} - struct ContentView: View { - let authService: AuthService - init() { let configuration = AuthConfiguration() @@ -376,6 +316,8 @@ struct ContentView: View { .withGoogleSignIn() .withAppleSignIn() } + + let authService: AuthService var body: some View { NavigationStack { From 493b1d33a2db9fdb5fcb9239c46281d2b1b11749 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:20:29 +0000 Subject: [PATCH 03/20] docs: update approach 2 --- FirebaseSwiftUI/first_draft.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md index c30590f49d..cd1cac7f03 100644 --- a/FirebaseSwiftUI/first_draft.md +++ b/FirebaseSwiftUI/first_draft.md @@ -350,13 +350,13 @@ 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 { @@ -367,8 +367,7 @@ struct CustomTwitterButton: View { } } } label: { - HStack { - Image("twitter-logo") // Your custom icon + HStack { // Your custom icon Text("Sign in with Twitter") .fontWeight(.semibold) } @@ -393,11 +392,11 @@ 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)) } @@ -406,23 +405,36 @@ class CustomTwitterProviderAuthUI: AuthProviderUI { // 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 { - Text("Welcome!") + usersApp } .environment(authService) } + + var usersApp: some View { + NavigationStack { + VStack { + Button { + authService.isPresented = true + } label: { + Text("Authenticate") + } + } + } + } } ``` From fb3dff9764c78a6267b97dcd48b9be896347975a Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:24:01 +0000 Subject: [PATCH 04/20] docs: change order of customisation --- FirebaseSwiftUI/first_draft.md | 122 ++++++++++++++++----------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md index cd1cac7f03..8056b7cf8d 100644 --- a/FirebaseSwiftUI/first_draft.md +++ b/FirebaseSwiftUI/first_draft.md @@ -272,67 +272,7 @@ The default views support: If you need more control over the UI or navigation flow, you can build your own custom authentication views while still leveraging the `AuthService` for authentication logic. -### Approach 1: Default Buttons with Custom Views - -You can use `AuthService.renderButtons()` to render the default authentication buttons while providing your own layout and navigation: - -```swift -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) - } -} -``` - -### Approach 2: Custom Buttons with `registerProvider()` +### Approach 1: Custom Buttons with `registerProvider()` For complete control over button appearance, you can create your own custom `AuthProviderUI` implementation that wraps any provider and returns your custom button view. @@ -512,6 +452,66 @@ struct ContentView: View { This approach works for all providers: Google, Apple, Twitter, Facebook, Phone, and OAuth providers. Simply create your custom button view and wrap it in a class conforming to `AuthProviderUI`. +### Approach 2: Default Buttons with Custom Views + +You can use `AuthService.renderButtons()` to render the default authentication buttons while providing your own layout and navigation: + +```swift +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) + } +} +``` + ### Approach 3: Custom Views with Custom Navigation For complete control over the entire flow, you can bypass `AuthPickerView` and build your own navigation system: From f1ae8eb0a92d4b70c3b25b3772cd85ab4aaea52b Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:42:30 +0000 Subject: [PATCH 05/20] docs: approach 3 --- FirebaseSwiftUI/first_draft.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md index 8056b7cf8d..f706dfbcc8 100644 --- a/FirebaseSwiftUI/first_draft.md +++ b/FirebaseSwiftUI/first_draft.md @@ -454,7 +454,7 @@ This approach works for all providers: Google, Apple, Twitter, Facebook, Phone, ### Approach 2: Default Buttons with Custom Views -You can use `AuthService.renderButtons()` to render the default authentication buttons while providing your own layout and navigation: +You can use `AuthService.renderButtons()` and bypass `AuthPickerView` to render the default authentication buttons while providing your own layout and navigation: ```swift import FirebaseAuthSwiftUI @@ -517,6 +517,7 @@ struct ContentView: View { For complete control over the entire flow, you can bypass `AuthPickerView` and build your own navigation system: ```swift +import FirebaseAuth import FirebaseAuthSwiftUI import FirebaseGoogleSwiftUI import SwiftUI @@ -579,7 +580,7 @@ struct ContentView: View { Button("Sign in with Google") { Task { do { - let provider = GoogleProviderSwift() + let provider = GoogleProviderSwift( clientID: Auth.auth().app?.options.clientID ?? "") let outcome = try await authService.signIn(provider) // Handle MFA if required From 3f3b3c1e7f30b496d5aa9205ef88ba3dc30b090e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:49:08 +0000 Subject: [PATCH 06/20] docs: improve approach 3 --- FirebaseSwiftUI/first_draft.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md index f706dfbcc8..6b031989f8 100644 --- a/FirebaseSwiftUI/first_draft.md +++ b/FirebaseSwiftUI/first_draft.md @@ -529,17 +529,15 @@ enum CustomAuthRoute { } struct ContentView: View { - @State private var authService: AuthService + private let authService: AuthService @State private var navigationPath: [CustomAuthRoute] = [] @State private var errorMessage: String? init() { let configuration = AuthConfiguration() - let service = AuthService(configuration: configuration) + self.authService = AuthService(configuration: configuration) .withGoogleSignIn() .withPhoneSignIn() - - _authService = State(initialValue: service) } var body: some View { From 0a56dd6924e8f26deab2102aa3fbd8a7ec16c931 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:49:44 +0000 Subject: [PATCH 07/20] doc: remove line --- FirebaseSwiftUI/first_draft.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md index 6b031989f8..43a00e437a 100644 --- a/FirebaseSwiftUI/first_draft.md +++ b/FirebaseSwiftUI/first_draft.md @@ -633,9 +633,8 @@ When building custom views, you need to handle several things yourself that `Aut 1. **Account Conflicts**: Implement your own conflict resolution strategy using `AuthServiceError.accountConflict` 2. **MFA Handling**: Check `SignInOutcome` for `.mfaRequired` and handle MFA resolution manually 3. **Anonymous User Upgrades**: Handle the linking of anonymous accounts if `shouldAutoUpgradeAnonymousUsers` is enabled -4. **Error Display**: Catch and display errors from `AuthService` methods appropriately -5. **Navigation State**: Manage navigation between different auth screens (phone verification, password recovery, etc.) -6. **Loading States**: Show loading indicators during async authentication operations by observing `authService.authenticationState` +4. **Navigation State**: Manage navigation between different auth screens (phone verification, password recovery, etc.) +5. **Loading States**: Show loading indicators during async authentication operations by observing `authService.authenticationState` ### Custom OAuth Providers From 948fd892e16e9cd5fed77bc6bacbb1cc7f4c1560 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:57:01 +0000 Subject: [PATCH 08/20] docs: improve OAuth documentation --- FirebaseSwiftUI/first_draft.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/first_draft.md index 43a00e437a..0e7422177a 100644 --- a/FirebaseSwiftUI/first_draft.md +++ b/FirebaseSwiftUI/first_draft.md @@ -640,6 +640,8 @@ When building custom views, you need to handle several things yourself that `Aut You can create custom OAuth providers for services beyond the built-in ones: +> **⚠️ Important:** OIDC (OpenID Connect) providers must be configured in your Firebase project's Authentication settings before they can be used. In the Firebase Console, go to **Authentication → Sign-in method** and add your OIDC provider with the required credentials (Client ID, Client Secret, Issuer URL). You must also register the OAuth redirect URI provided by Firebase in your provider's developer console. See the [Firebase OIDC documentation](https://firebase.google.com/docs/auth/ios/openid-connect) for detailed setup instructions. + ```swift import FirebaseAuthSwiftUI import FirebaseOAuthSwiftUI @@ -655,6 +657,7 @@ struct ContentView: View { .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, @@ -664,6 +667,7 @@ struct ContentView: View { .withOAuthSignIn( OAuthProviderSwift( providerId: "oidc.custom-provider", + scopes: ["profile", "openid"], displayName: "Sign in with Custom", buttonIcon: Image(systemName: "person.circle"), buttonBackgroundColor: .purple, From 8907e2f0d6073b94c4da4366b6435ca84f65dc1b Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 14:57:49 +0000 Subject: [PATCH 09/20] docs: rename --- FirebaseSwiftUI/{first_draft.md => documentation.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename FirebaseSwiftUI/{first_draft.md => documentation.md} (100%) diff --git a/FirebaseSwiftUI/first_draft.md b/FirebaseSwiftUI/documentation.md similarity index 100% rename from FirebaseSwiftUI/first_draft.md rename to FirebaseSwiftUI/documentation.md From 1acdde92c868d545b634440a6ebfc6d4f50ff493 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 15:00:52 +0000 Subject: [PATCH 10/20] docs: clean up --- FirebaseSwiftUI/documentation.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/FirebaseSwiftUI/documentation.md b/FirebaseSwiftUI/documentation.md index 0e7422177a..b17f496a18 100644 --- a/FirebaseSwiftUI/documentation.md +++ b/FirebaseSwiftUI/documentation.md @@ -1511,9 +1511,7 @@ Errors specific to `AuthService` operations. 5. **Observe authentication state**: Use `onChange(of: authService.authenticationState)` to react to authentication changes. -6. **Error handling in custom views**: When not using `AuthPickerView`, implement comprehensive error handling. - -7. **Provider-specific setup**: Some providers (Google, Facebook) require additional configuration in AppDelegate or Info.plist. See the [sample app](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui) for examples. +6. **Provider-specific setup**: Some providers (Google, Facebook) require additional configuration in AppDelegate or Info.plist. See the [sample app](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui) for examples. --- @@ -1528,5 +1526,5 @@ Errors specific to `AuthService` operations. ## Feedback -This is an alpha release. Please file feedback and issues in the [repository's issue tracker](https://github.com/firebase/FirebaseUI-iOS/issues). +Please file feedback and issues in the [repository's issue tracker](https://github.com/firebase/FirebaseUI-iOS/issues). From 79b4dfbd39a729387d97a776b3e0b58e21a5a601 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 10 Nov 2025 14:24:02 +0000 Subject: [PATCH 11/20] docs: move documentation.md to README.md --- FirebaseSwiftUI/README.md | 1575 ++++++++++++++++++++++++++++-- FirebaseSwiftUI/documentation.md | 1530 ----------------------------- 2 files changed, 1480 insertions(+), 1625 deletions(-) delete mode 100644 FirebaseSwiftUI/documentation.md diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index b06a55a363..b17f496a18 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -1,19 +1,48 @@ -# FirebaseUI for SwiftUI (Alpha release) +# FirebaseUI for SwiftUI Documentation + +## Table of Contents + +1. [Installation](#installation) +2. [Getting Started](#getting-started) +3. [Usage with Default Views](#usage-with-default-views) +4. [Usage with Custom Views](#usage-with-custom-views) +5. [API Reference](#api-reference) + +--- ## Installation -1. Launch Xcode and open the project or workspace where you want to add the packages. -2. In the menu bar, go to: `File > Add Package Dependencies...` +### Using Swift Package Manager + +1. Launch Xcode and open the project or workspace where you want to add FirebaseUI for SwiftUI. +2. In the menu bar, go to: **File > Add Package Dependencies...** 3. Enter the Package URL: `https://github.com/firebase/FirebaseUI-iOS` -4. In the `Dependency Rule` dropdown, select `Exact Version` and set the version to `15.1.0-alpha` in the resulting text input. -5. Select target(s) you wish to add to your app (currently `FirebaseAuthSwiftUI`, `FirebaseGoogleSwiftUI`, `FirebaseFacebookSwiftUI` and `FirebasePhoneAuthSwiftUI` are available). `FirebaseAuthSwiftUI` is required and contains the Email provider API. -6. Press the `Add Packages` button to complete installation. +4. In the **Dependency Rule** dropdown, select **Exact Version** and set the version to `15.1.0-alpha` in the resulting text input. +5. Select the targets you wish to add to your app. The available SwiftUI packages are: + - **FirebaseAuthSwiftUI** (required - includes Email auth provider) + - **FirebaseAppleSwiftUI** (Sign in with Apple) + - **FirebaseGoogleSwiftUI** (Sign in with Google) + - **FirebaseFacebookSwiftUI** (Sign in with Facebook) + - **FirebasePhoneAuthSwiftUI** (Phone authentication) + - **FirebaseTwitterSwiftUI** (Sign in with Twitter) + - **FirebaseOAuthSwiftUI** (Generic OAuth providers like GitHub, Microsoft, Yahoo) +6. Press the **Add Packages** button to complete installation. + +### Platform Requirements + +- **Minimum iOS Version**: iOS 17+ +- **Swift Version**: Swift 6.0+ + +--- + +## Getting Started +### Basic Setup -## Getting started +Before using FirebaseUI for SwiftUI, you need to configure Firebase in your app: -1. Follow step 2, 3 & 5 on [adding Firebase to your SwiftUI app](https://firebase.google.com/docs/ios/setup). -2. You should now update your app entry point to look like this: +1. Follow steps 2, 3 & 5 in [adding Firebase to your iOS app](https://firebase.google.com/docs/ios/setup). +2. Update your app entry point: ```swift import FirebaseAuthSwiftUI @@ -21,17 +50,17 @@ import FirebaseCore import SwiftUI class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [ - UIApplication.LaunchOptionsKey: Any - ]?) -> Bool { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { FirebaseApp.configure() return true } } @main -struct FirebaseSwiftUIExampleApp: App { +struct YourApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { @@ -40,106 +69,1462 @@ struct FirebaseSwiftUIExampleApp: App { } } } +``` + +--- + +## Usage with Default Views + +FirebaseUI for SwiftUI provides `AuthPickerView`, a pre-built, opinionated authentication UI that handles the entire authentication flow for you. This is the easiest way to add authentication to your app. + +### Minimal Example + +Here's a minimal example using the default views with email authentication: + +```swift +import FirebaseAuthSwiftUI +import SwiftUI + +struct ContentView: View { + let authService: AuthService + + init() { + let configuration = AuthConfiguration() + + authService = AuthService(configuration: configuration) + .withEmailSignIn() + } + + var body: some View { + AuthPickerView { + // Your authenticated app content goes here + Text("Welcome to your app!") + } + .environment(authService) + } +} +``` + +### Full-Featured Example + +Here's a more complete example with multiple providers and configuration options: + +```swift +import FirebaseAppleSwiftUI +import FirebaseAuthSwiftUI +import FirebaseFacebookSwiftUI +import FirebaseGoogleSwiftUI +import FirebaseOAuthSwiftUI +import FirebasePhoneAuthSwiftUI +import FirebaseTwitterSwiftUI +import SwiftUI + +struct ContentView: View { + let authService: AuthService + + init() { + // Configure email link sign-in + let actionCodeSettings = ActionCodeSettings() + actionCodeSettings.handleCodeInApp = true + actionCodeSettings.url = URL(string: "https://yourapp.firebaseapp.com") + actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!) + + // Create configuration with options + let configuration = AuthConfiguration( + shouldAutoUpgradeAnonymousUsers: true, + tosUrl: URL(string: "https://example.com/tos"), + privacyPolicyUrl: URL(string: "https://example.com/privacy"), + emailLinkSignInActionCodeSettings: actionCodeSettings, + mfaEnabled: true + ) + + // Initialize AuthService with multiple providers + authService = AuthService(configuration: configuration) + .withEmailSignIn() + .withAppleSignIn() + .withGoogleSignIn() + .withFacebookSignIn(FacebookProviderSwift()) + .withPhoneSignIn() + .withTwitterSignIn() + .withOAuthSignIn(OAuthProviderSwift.github()) + .withOAuthSignIn(OAuthProviderSwift.microsoft()) + .withOAuthSignIn(OAuthProviderSwift.yahoo()) + } + + 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) + } + } + } +} +``` + +### How Default Views Work + +When you use `AuthPickerView`, you get: + +1. **Sheet Presentation**: Authentication UI appears as a modal sheet +2. **Built-in Navigation**: Automatic navigation between sign-in, password recovery, MFA, email link, and phone verification screens +3. **Authentication State Management**: Automatically switches between auth UI and your content based on `authService.authenticationState` +4. **Control via `isPresented`**: Control when the auth sheet appears by setting `authService.isPresented = true/false` + +### Opinionated Behaviors in Default Views + +The default `AuthPickerView` handles several complex scenarios automatically: + +#### 1. **Account Conflict Resolution** + +When an account conflict occurs (e.g., signing in with a credential that's already linked to another account), `AuthPickerView` automatically handles it: + +- **Anonymous Upgrade Conflicts**: If `shouldAutoUpgradeAnonymousUsers` is enabled and a conflict occurs during anonymous upgrade, the system automatically signs out the anonymous user and signs in with the new credential. +- **Other Conflicts**: For credential conflicts between non-anonymous accounts, the system stores the pending credential and attempts to link it after successful sign-in. + +This is handled by the `AccountConflictModifier` applied at the NavigationStack level. + +#### 2. **Multi-Factor Authentication (MFA)** + +When MFA is enabled in your configuration: + +- Automatically detects when MFA is required during sign-in +- Presents appropriate MFA resolution screens (SMS or TOTP) +- Handles MFA enrollment and management flows +- Supports both SMS-based and Time-based One-Time Password (TOTP) factors + +#### 3. **Error Handling** + +The default views include built-in error handling: + +- Displays user-friendly error messages in alert dialogs +- Automatically filters out errors that are handled internally (e.g., cancellation errors, auto-handled conflicts) +- Uses localized error messages via `StringUtils` +- Errors are propagated through the `reportError` environment key + +#### 4. **Email Link Sign-In** + +When email link sign-in is configured: + +- Automatically stores the email address in app storage +- Handles deep link navigation from email +- Manages the complete email verification flow +- Supports anonymous user upgrades via email link + +#### 5. **Anonymous User Auto-Upgrade** + +When `shouldAutoUpgradeAnonymousUsers` is enabled: + +- Automatically attempts to link anonymous accounts with new sign-in credentials +- Preserves user data by upgrading instead of replacing anonymous sessions +- Handles upgrade conflicts gracefully + +### Available Auth Methods in Default Views + +The default views support: + +- **Email/Password Authentication** (built into `FirebaseAuthSwiftUI`) +- **Email Link Authentication** (passwordless) +- **Phone Authentication** (SMS verification) +- **Sign in with Apple** +- **Sign in with Google** +- **Sign in with Facebook** (Classic and Limited Login) +- **Sign in with Twitter** +- **Generic OAuth Providers** (GitHub, Microsoft, Yahoo, or custom OIDC) + +--- + +## Usage with Custom Views + +If you need more control over the UI or navigation flow, you can build your own custom authentication views while still leveraging the `AuthService` for authentication logic. + +### Approach 1: Custom Buttons with `registerProvider()` + +For complete control over button appearance, you can create your own custom `AuthProviderUI` implementation that wraps any provider and returns your custom button view. + +#### Creating a Custom Provider UI + +Here's how to create a custom Twitter button as an example: + +```swift +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") + } + } + } + } +} +``` + +#### Simplified Custom Button Example + +You can also create simpler custom buttons for any provider: + +```swift +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) + } + } +} - authService = AuthService( - configuration: configuration, +// 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() ) - .withEmailSignIn() + } + + var body: some View { + AuthPickerView { + Text("App Content") + } + .environment(authService) + } +} +``` + +This approach works for all providers: Google, Apple, Twitter, Facebook, Phone, and OAuth providers. Simply create your custom button view and wrap it in a class conforming to `AuthProviderUI`. + +### Approach 2: Default Buttons with Custom Views + +You can use `AuthService.renderButtons()` and bypass `AuthPickerView` to render the default authentication buttons while providing your own layout and navigation: + +```swift +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) + } +} +``` + +### Approach 3: Custom Views with Custom Navigation + +For complete control over the entire flow, you can bypass `AuthPickerView` and build your own navigation system: + +```swift +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) + } + } +} +``` + +### Important Considerations for Custom Views + +When building custom views, you need to handle several things yourself that `AuthPickerView` handles automatically: + +1. **Account Conflicts**: Implement your own conflict resolution strategy using `AuthServiceError.accountConflict` +2. **MFA Handling**: Check `SignInOutcome` for `.mfaRequired` and handle MFA resolution manually +3. **Anonymous User Upgrades**: Handle the linking of anonymous accounts if `shouldAutoUpgradeAnonymousUsers` is enabled +4. **Navigation State**: Manage navigation between different auth screens (phone verification, password recovery, etc.) +5. **Loading States**: Show loading indicators during async authentication operations by observing `authService.authenticationState` + +### Custom OAuth Providers + +You can create custom OAuth providers for services beyond the built-in ones: + +> **⚠️ Important:** OIDC (OpenID Connect) providers must be configured in your Firebase project's Authentication settings before they can be used. In the Firebase Console, go to **Authentication → Sign-in method** and add your OIDC provider with the required credentials (Client ID, Client Secret, Issuer URL). You must also register the OAuth redirect URI provided by Firebase in your provider's developer console. See the [Firebase OIDC documentation](https://firebase.google.com/docs/auth/ios/openid-connect) for detailed setup instructions. + +```swift +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().environment(authService) + AuthPickerView { + Text("App Content") + } + .environment(authService) } } ``` -3. For a more complete example, see the [SwiftUI sample app](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui/FirebaseSwiftUIExample). +--- -## Configuration options +## API Reference -You can create an `AuthConfiguration` instance and pass it to the `AuthService` as demonstrated above. Here are the options: +### AuthConfiguration + +The `AuthConfiguration` struct allows you to customize the behavior of the authentication flow. ```swift public struct AuthConfiguration { - // hides cancel buttons when you don't want a flow to be interrupted - let shouldHideCancelButton: Bool - // stop users from being able to swipe away sheets/modal - let interactiveDismissEnabled: Bool - // automatically upgrade anonymous users so that they are linked with account being used to sign-in - let shouldAutoUpgradeAnonymousUsers: Bool - // custom string bundle for string localizations - let customStringsBundle: Bundle? - // terms of service URL - let tosUrl: URL? - // privacy policy URL - let privacyPolicyUrl: URL? - // action code settings for email sign in link - let emailLinkSignInActionCodeSettings: ActionCodeSettings? - // action code settings verifying email address - let verifyEmailActionCodeSettings: ActionCodeSettings? - - public init(shouldHideCancelButton: Bool = false, - interactiveDismissEnabled: Bool = true, - shouldAutoUpgradeAnonymousUsers: Bool = false, - customStringsBundle: Bundle? = nil, - tosUrl: URL? = nil, - privacyPolicyUrl: URL? = nil, - emailLinkSignInActionCodeSettings: ActionCodeSettings? = nil, - verifyEmailActionCodeSettings: ActionCodeSettings? = nil) -} -``` - -> Note: Both `tosUrl` and `privacyPolicyUrl` have to be set for them to be rendered in the UI. - -## Configuring providers - -1. Ensure the provider is installed from step 1 (e.g. if configuring Google provider, you need to install `FirebaseGoogleSwiftUI` package). -2. Ensure you have called the relevant API on `AuthService` to initialise the provider. Example of Email and Google provider initialization: - -```swift -let authService = AuthService() - .withEmailSignIn() - .withGoogleSignIn() -``` - -> Note: There may be additional setup for each provider typically in the AppDelegate. [See example app for setup.](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui/FirebaseSwiftUIExample) - - -## API available for alpha release -1. General API - - Auto upgrade anonymous user account linking (if configured). - - Sign out -2. Email/Password - - Sign-in/Create user - - Password recovery - - Email link sign-in -3. Google - - Sign in with Google -4. Facebook - 1. Sign in with Facebook limited login - 2. Sign in with Facebook classic login -5. Phone Auth - - Verify phone number - - Sign in with phone number -6. User - - Update password - - Delete user - - Verify email address - - -## Notes for Alpha release -1. Customization/theming for Views is not available directly in the default view. - To create a custom view, build your own auth picker view from the provider components. -2. The providers available are Email, Phone, Google and Facebook. -3. String localizations have been ported over and used where possible from the previous implementation, but new strings will only have English translations for the time being. -4. The UI has not been polished and is subject to change once design has been finalized. + public init( + logo: ImageResource? = nil, + languageCode: String? = nil, + shouldHideCancelButton: Bool = false, + interactiveDismissEnabled: Bool = true, + shouldAutoUpgradeAnonymousUsers: Bool = false, + customStringsBundle: Bundle? = nil, + tosUrl: URL? = nil, + privacyPolicyUrl: URL? = nil, + emailLinkSignInActionCodeSettings: ActionCodeSettings? = nil, + verifyEmailActionCodeSettings: ActionCodeSettings? = nil, + mfaEnabled: Bool = false, + allowedSecondFactors: Set = [.sms, .totp], + mfaIssuer: String = "Firebase Auth" + ) +} +``` -## Feedback +#### Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `logo` | `ImageResource?` | `nil` | Custom logo to display in the authentication UI. If not provided, the default Firebase logo is used. | +| `languageCode` | `String?` | `nil` | Language code for localized strings (e.g., "en", "es", "fr"). If not provided, uses system language. | +| `shouldHideCancelButton` | `Bool` | `false` | When `true`, hides the cancel button in auth flows, preventing users from dismissing the UI. Useful for mandatory authentication. | +| `interactiveDismissEnabled` | `Bool` | `true` | When `false`, prevents users from dismissing auth sheets by swiping down. | +| `shouldAutoUpgradeAnonymousUsers` | `Bool` | `false` | When `true`, automatically links anonymous user accounts with new sign-in credentials, preserving any data associated with the anonymous session. | +| `customStringsBundle` | `Bundle?` | `nil` | Custom bundle for string localizations. Allows you to override default strings with your own translations. | +| `tosUrl` | `URL?` | `nil` | URL to your Terms of Service. When both `tosUrl` and `privacyPolicyUrl` are set, links are displayed in the auth UI. | +| `privacyPolicyUrl` | `URL?` | `nil` | URL to your Privacy Policy. When both `tosUrl` and `privacyPolicyUrl` are set, links are displayed in the auth UI. | +| `emailLinkSignInActionCodeSettings` | `ActionCodeSettings?` | `nil` | Configuration for email link (passwordless) sign-in. Must be set to use email link authentication. | +| `verifyEmailActionCodeSettings` | `ActionCodeSettings?` | `nil` | Configuration for email verification. Used when sending verification emails to users. | +| `mfaEnabled` | `Bool` | `false` | Enables Multi-Factor Authentication support. When enabled, users can enroll in and use MFA. | +| `allowedSecondFactors` | `Set` | `[.sms, .totp]` | Set of allowed MFA factor types. Options are `.sms` (phone-based) and `.totp` (authenticator app). | +| `mfaIssuer` | `String` | `"Firebase Auth"` | The issuer name displayed in TOTP authenticator apps when users enroll. | + +#### Notes + +- Both `tosUrl` and `privacyPolicyUrl` must be set for the links to appear in the UI. +- `emailLinkSignInActionCodeSettings` is **required** if you want to use email link sign-in. The `ActionCodeSettings` must have: + - `handleCodeInApp = true` + - A valid `url` + - iOS bundle ID configured via `setIOSBundleID()` + +--- + +### AuthService + +The main service class that manages authentication state and operations. + +#### Initialization + +```swift +public init( + configuration: AuthConfiguration = AuthConfiguration(), + auth: Auth = Auth.auth() +) +``` + +Creates a new `AuthService` instance. + +**Parameters:** +- `configuration`: Configuration for auth behavior (default: `AuthConfiguration()`) +- `auth`: Firebase Auth instance to use (default: `Auth.auth()`) + +--- + +#### Configuring Providers + +##### Email Authentication + +```swift +public func withEmailSignIn() -> AuthService +``` + +Enables email/password and email link authentication. Uses default behavior (navigates to email link view when tapped). + +```swift +public func withEmailSignIn(onTap: @escaping @MainActor () -> Void) -> AuthService +``` + +Enables email authentication with a custom callback when the email button is tapped. + +**Example:** + +```swift +authService + .withEmailSignIn() +``` + +--- + +##### Phone Authentication + +```swift +// Available when importing FirebasePhoneAuthSwiftUI +public func withPhoneSignIn() -> AuthService +``` + +Enables phone number authentication with SMS verification. + +**Example:** + +```swift +authService + .withPhoneSignIn() +``` + +--- + +##### Sign in with Apple + +```swift +// Available when importing FirebaseAppleSwiftUI +public func withAppleSignIn() -> AuthService +``` + +Enables Sign in with Apple authentication. + +**Example:** + +```swift +authService + .withAppleSignIn() +``` + +--- + +##### Sign in with Google + +```swift +// Available when importing FirebaseGoogleSwiftUI +public func withGoogleSignIn() -> AuthService +``` + +Enables Sign in with Google authentication. + +**Example:** + +```swift +authService + .withGoogleSignIn() +``` + +--- + +##### Sign in with Facebook + +```swift +// Available when importing FirebaseFacebookSwiftUI +public func withFacebookSignIn(_ provider: FacebookProviderSwift) -> AuthService +``` + +Enables Sign in with Facebook authentication. + +**Parameters:** +- `provider`: An instance of `FacebookProviderSwift()` for classic login or `FacebookProviderSwift(useClassicLogin: false)` for limited login. + +**Example:** + +```swift +authService + .withFacebookSignIn(FacebookProviderSwift()) +``` + +--- + +##### Sign in with Twitter + +```swift +// Available when importing FirebaseTwitterSwiftUI +public func withTwitterSignIn() -> AuthService +``` + +Enables Sign in with Twitter authentication. + +**Example:** + +```swift +authService + .withTwitterSignIn() +``` + +--- + +##### Generic OAuth Providers + +```swift +// Available when importing FirebaseOAuthSwiftUI +public func withOAuthSignIn(_ provider: OAuthProviderSwift) -> AuthService +``` + +Enables authentication with generic OAuth/OIDC providers. + +**Built-in Providers:** +- `OAuthProviderSwift.github()` +- `OAuthProviderSwift.microsoft()` +- `OAuthProviderSwift.yahoo()` + +**Custom Provider:** + +```swift +OAuthProviderSwift( + providerId: String, + displayName: String, + buttonIcon: Image, + buttonBackgroundColor: Color, + buttonForegroundColor: Color +) +``` + +**Example:** + +```swift +authService + .withOAuthSignIn(OAuthProviderSwift.github()) + .withOAuthSignIn(OAuthProviderSwift.microsoft()) + .withOAuthSignIn( + OAuthProviderSwift( + providerId: "oidc.custom-provider", + displayName: "Sign in with Custom", + buttonIcon: Image("custom-logo"), + buttonBackgroundColor: .blue, + buttonForegroundColor: .white + ) + ) +``` + +--- + +#### Custom Provider Registration + +```swift +public func registerProvider(providerWithButton: AuthProviderUI) +``` + +Registers a custom authentication provider that conforms to `AuthProviderUI`. + +**Parameters:** +- `providerWithButton`: A custom provider implementing the `AuthProviderUI` protocol. + +**Example:** + +```swift +let customProvider = MyCustomProvider() +authService.registerProvider(providerWithButton: customProvider) +``` + +--- + +#### Rendering Authentication Buttons + +```swift +public func renderButtons(spacing: CGFloat = 16) -> AnyView +``` + +Renders all registered authentication provider buttons as a vertical stack. + +**Parameters:** +- `spacing`: Vertical spacing between buttons (default: 16) + +**Returns:** An `AnyView` containing all auth buttons. + +**Example:** + +```swift +VStack { + Text("Choose a sign-in method") + authService.renderButtons(spacing: 12) +} +``` + +--- + +#### Authentication Operations + +##### Sign In with Credential + +```swift +public func signIn(_ provider: CredentialAuthProviderSwift) async throws -> SignInOutcome +``` + +Signs in using a provider that conforms to `CredentialAuthProviderSwift`. + +**Parameters:** +- `provider`: The authentication provider to use. + +**Returns:** `SignInOutcome` - either `.signedIn(AuthDataResult?)` or `.mfaRequired(MFARequired)` + +**Throws:** `AuthServiceError` or Firebase Auth errors + +**Example:** + +```swift +Task { + do { + let outcome = try await authService.signIn(GoogleProviderSwift()) + switch outcome { + case .signedIn(let result): + print("Signed in: \(result?.user.email ?? "")") + case .mfaRequired(let mfaInfo): + // Handle MFA resolution + print("MFA required: \(mfaInfo)") + } + } catch { + print("Sign in error: \(error)") + } +} +``` + +--- + +##### Sign In with Email/Password + +```swift +public func signIn(email: String, password: String) async throws -> SignInOutcome +``` + +Signs in using email and password credentials. + +**Parameters:** +- `email`: User's email address +- `password`: User's password + +**Returns:** `SignInOutcome` + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +##### Create User with Email/Password + +```swift +public func createUser(email: String, password: String) async throws -> SignInOutcome +``` + +Creates a new user account with email and password. + +**Parameters:** +- `email`: New user's email address +- `password`: New user's password + +**Returns:** `SignInOutcome` + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +##### Sign Out + +```swift +public func signOut() async throws +``` + +Signs out the current user. + +**Throws:** Firebase Auth errors + +**Example:** + +```swift +Button("Sign Out") { + Task { + try await authService.signOut() + } +} +``` + +--- + +##### Link Accounts + +```swift +public func linkAccounts(credentials: AuthCredential) async throws +``` + +Links a new authentication method to the current user's account. + +**Parameters:** +- `credentials`: The credential to link + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +#### Email Link (Passwordless) Authentication + +##### Send Email Sign-In Link + +```swift +public func sendEmailSignInLink(email: String) async throws +``` + +Sends a sign-in link to the specified email address. + +**Parameters:** +- `email`: Email address to send the link to + +**Throws:** `AuthServiceError` or Firebase Auth errors + +**Requirements:** `emailLinkSignInActionCodeSettings` must be configured in `AuthConfiguration` + +--- + +##### Handle Sign-In Link + +```swift +public func handleSignInLink(url: URL) async throws +``` + +Handles the sign-in flow when the user taps the email link. + +**Parameters:** +- `url`: The deep link URL from the email + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +#### Phone Authentication + +##### Verify Phone Number + +```swift +public func verifyPhoneNumber(phoneNumber: String) async throws -> String +``` + +Sends a verification code to the specified phone number. + +**Parameters:** +- `phoneNumber`: Phone number in E.164 format (e.g., "+15551234567") + +**Returns:** Verification ID to use when verifying the code + +**Throws:** Firebase Auth errors + +--- + +##### Sign In with Phone Number + +```swift +public func signInWithPhoneNumber( + verificationID: String, + verificationCode: String +) async throws +``` + +Signs in using a phone number and verification code. + +**Parameters:** +- `verificationID`: The verification ID returned from `verifyPhoneNumber()` +- `verificationCode`: The SMS code received by the user + +**Throws:** `AuthServiceError` or Firebase Auth errors + +--- + +#### User Profile Management + +##### Update Display Name + +```swift +public func updateUserDisplayName(name: String) async throws +``` + +Updates the current user's display name. + +**Parameters:** +- `name`: New display name + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Update Photo URL + +```swift +public func updateUserPhotoURL(url: URL) async throws +``` + +Updates the current user's photo URL. + +**Parameters:** +- `url`: URL to the user's profile photo + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Update Password + +```swift +public func updatePassword(to password: String) async throws +``` + +Updates the current user's password. May require recent authentication. + +**Parameters:** +- `password`: New password + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Send Email Verification + +```swift +public func sendEmailVerification() async throws +``` + +Sends a verification email to the current user's email address. + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Delete User + +```swift +public func deleteUser() async throws +``` + +Deletes the current user's account. May require recent authentication. + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +#### Multi-Factor Authentication (MFA) + +##### Start MFA Enrollment + +```swift +public func startMfaEnrollment( + type: SecondFactorType, + accountName: String? = nil, + issuer: String? = nil +) async throws -> EnrollmentSession +``` + +Initiates enrollment for a second factor. + +**Parameters:** +- `type`: Type of second factor (`.sms` or `.totp`) +- `accountName`: Account name for TOTP (defaults to user's email) +- `issuer`: Issuer name for TOTP (defaults to `configuration.mfaIssuer`) + +**Returns:** `EnrollmentSession` containing enrollment information + +**Throws:** `AuthServiceError` if MFA is not enabled or factor type not allowed + +**Requirements:** `mfaEnabled` must be `true` in `AuthConfiguration` + +--- + +##### Send SMS Verification for Enrollment + +```swift +public func sendSmsVerificationForEnrollment( + session: EnrollmentSession, + phoneNumber: String +) async throws -> String +``` + +Sends SMS verification code during MFA enrollment (for SMS-based second factor). + +**Parameters:** +- `session`: The enrollment session from `startMfaEnrollment()` +- `phoneNumber`: Phone number to enroll (E.164 format) + +**Returns:** Verification ID for completing enrollment + +**Throws:** `AuthServiceError` + +--- + +##### Complete MFA Enrollment + +```swift +public func completeEnrollment( + session: EnrollmentSession, + verificationId: String?, + verificationCode: String, + displayName: String +) async throws +``` + +Completes the MFA enrollment process. + +**Parameters:** +- `session`: The enrollment session +- `verificationId`: Verification ID (required for SMS, ignored for TOTP) +- `verificationCode`: The verification code from SMS or TOTP app +- `displayName`: Display name for this MFA factor + +**Throws:** `AuthServiceError` + +--- + +##### Unenroll MFA Factor + +```swift +public func unenrollMFA(_ factorUid: String) async throws -> [MultiFactorInfo] +``` + +Removes an MFA factor from the user's account. + +**Parameters:** +- `factorUid`: UID of the factor to remove + +**Returns:** Updated list of remaining enrolled factors + +**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors + +--- + +##### Resolve MFA Challenge (SMS) + +```swift +public func resolveSmsChallenge(hintIndex: Int) async throws -> String +``` + +Sends SMS code for resolving an MFA challenge during sign-in. + +**Parameters:** +- `hintIndex`: Index of the MFA hint to use + +**Returns:** Verification ID for completing sign-in + +**Throws:** `AuthServiceError` + +--- + +##### Resolve Sign-In with MFA + +```swift +public func resolveSignIn( + code: String, + hintIndex: Int, + verificationId: String? = nil +) async throws +``` + +Completes sign-in by verifying the MFA code. + +**Parameters:** +- `code`: The MFA code from SMS or TOTP app +- `hintIndex`: Index of the MFA hint being used +- `verificationId`: Verification ID (required for SMS, ignored for TOTP) + +**Throws:** `AuthServiceError` + +--- + +#### Public Properties + +```swift +public let configuration: AuthConfiguration +``` +The configuration used by this service. + +--- + +```swift +public let auth: Auth +``` +The Firebase Auth instance. + +--- + +```swift +public var isPresented: Bool +``` +Controls whether the authentication sheet is presented (when using `AuthPickerView`). + +--- + +```swift +public var currentUser: User? +``` +The currently signed-in Firebase user, or `nil` if not authenticated. + +--- + +```swift +public var authenticationState: AuthenticationState +``` +Current authentication state: `.unauthenticated`, `.authenticating`, or `.authenticated`. + +--- + +```swift +public var authenticationFlow: AuthenticationFlow +``` +Current flow type: `.signIn` or `.signUp`. + +--- + +```swift +public private(set) var navigator: Navigator +``` +Navigator for managing navigation routes in default views. + +--- + +```swift +public var authView: AuthView? +``` +Currently displayed auth view (e.g., `.emailLink`, `.mfaResolution`). + +--- + +### AuthPickerView + +A pre-built view that provides complete authentication UI. + +```swift +public struct AuthPickerView: View { + public init(@ViewBuilder content: @escaping () -> Content = { EmptyView() }) +} +``` + +**Parameters:** +- `content`: Your app's authenticated content, shown when user is signed in. + +**Usage:** + +```swift +AuthPickerView { + // Your app content here + Text("Welcome!") +} +.environment(authService) +``` + +**Behavior:** +- Presents authentication UI as a modal sheet controlled by `authService.isPresented` +- Automatically handles navigation between auth screens +- Includes built-in error handling and account conflict resolution +- Supports MFA flows automatically + +--- + +### Enums and Types + +#### AuthenticationState + +```swift +public enum AuthenticationState { + case unauthenticated + case authenticating + case authenticated +} +``` + +Represents the current authentication state. + +--- + +#### SignInOutcome + +```swift +public enum SignInOutcome { + case mfaRequired(MFARequired) + case signedIn(AuthDataResult?) +} +``` + +Result of a sign-in attempt. Either successful or requiring MFA. + +--- + +#### SecondFactorType + +```swift +public enum SecondFactorType { + case sms + case totp +} +``` + +Types of second factors for MFA. + +--- + +#### AuthServiceError + +```swift +public enum AuthServiceError: Error { + case noCurrentUser + case notConfiguredActionCodeSettings(String) + case invalidEmailLink(String) + case providerNotFound(String) + case invalidCredentials(String) + case multiFactorAuth(String) + case reauthenticationRequired(String) + case accountConflict(AccountConflictContext) +} +``` + +Errors specific to `AuthService` operations. + +--- + +### Best Practices + +1. **Initialize AuthService in the parent view**: Create `AuthService` once and pass it down via the environment. + +2. **Handle MFA outcomes**: Always check for `.mfaRequired` when calling sign-in methods if MFA is enabled. + +3. **Use ActionCodeSettings for email link**: Email link sign-in requires proper `ActionCodeSettings` configuration. + +4. **Test with anonymous users**: If using `shouldAutoUpgradeAnonymousUsers`, test the upgrade flow thoroughly. + +5. **Observe authentication state**: Use `onChange(of: authService.authenticationState)` to react to authentication changes. + +6. **Provider-specific setup**: Some providers (Google, Facebook) require additional configuration in AppDelegate or Info.plist. See the [sample app](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui) for examples. + +--- + +## Additional Resources + +- [Sample SwiftUI App](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui/FirebaseSwiftUIExample) +- [Firebase iOS Setup Guide](https://firebase.google.com/docs/ios/setup) +- [Firebase Authentication Documentation](https://firebase.google.com/docs/auth) +- [FirebaseUI-iOS GitHub Repository](https://github.com/firebase/FirebaseUI-iOS) + +--- + +## Feedback + +Please file feedback and issues in the [repository's issue tracker](https://github.com/firebase/FirebaseUI-iOS/issues). -Please file feedback in the repository's issue tracker. diff --git a/FirebaseSwiftUI/documentation.md b/FirebaseSwiftUI/documentation.md deleted file mode 100644 index b17f496a18..0000000000 --- a/FirebaseSwiftUI/documentation.md +++ /dev/null @@ -1,1530 +0,0 @@ -# FirebaseUI for SwiftUI Documentation - -## Table of Contents - -1. [Installation](#installation) -2. [Getting Started](#getting-started) -3. [Usage with Default Views](#usage-with-default-views) -4. [Usage with Custom Views](#usage-with-custom-views) -5. [API Reference](#api-reference) - ---- - -## Installation - -### Using Swift Package Manager - -1. Launch Xcode and open the project or workspace where you want to add FirebaseUI for SwiftUI. -2. In the menu bar, go to: **File > Add Package Dependencies...** -3. Enter the Package URL: `https://github.com/firebase/FirebaseUI-iOS` -4. In the **Dependency Rule** dropdown, select **Exact Version** and set the version to `15.1.0-alpha` in the resulting text input. -5. Select the targets you wish to add to your app. The available SwiftUI packages are: - - **FirebaseAuthSwiftUI** (required - includes Email auth provider) - - **FirebaseAppleSwiftUI** (Sign in with Apple) - - **FirebaseGoogleSwiftUI** (Sign in with Google) - - **FirebaseFacebookSwiftUI** (Sign in with Facebook) - - **FirebasePhoneAuthSwiftUI** (Phone authentication) - - **FirebaseTwitterSwiftUI** (Sign in with Twitter) - - **FirebaseOAuthSwiftUI** (Generic OAuth providers like GitHub, Microsoft, Yahoo) -6. Press the **Add Packages** button to complete installation. - -### Platform Requirements - -- **Minimum iOS Version**: iOS 17+ -- **Swift Version**: Swift 6.0+ - ---- - -## Getting Started - -### Basic Setup - -Before using FirebaseUI for SwiftUI, you need to configure Firebase in your app: - -1. Follow steps 2, 3 & 5 in [adding Firebase to your iOS app](https://firebase.google.com/docs/ios/setup). -2. Update your app entry point: - -```swift -import FirebaseAuthSwiftUI -import FirebaseCore -import SwiftUI - -class AppDelegate: NSObject, UIApplicationDelegate { - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - FirebaseApp.configure() - return true - } -} - -@main -struct YourApp: App { - @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` - ---- - -## Usage with Default Views - -FirebaseUI for SwiftUI provides `AuthPickerView`, a pre-built, opinionated authentication UI that handles the entire authentication flow for you. This is the easiest way to add authentication to your app. - -### Minimal Example - -Here's a minimal example using the default views with email authentication: - -```swift -import FirebaseAuthSwiftUI -import SwiftUI - -struct ContentView: View { - let authService: AuthService - - init() { - let configuration = AuthConfiguration() - - authService = AuthService(configuration: configuration) - .withEmailSignIn() - } - - var body: some View { - AuthPickerView { - // Your authenticated app content goes here - Text("Welcome to your app!") - } - .environment(authService) - } -} -``` - -### Full-Featured Example - -Here's a more complete example with multiple providers and configuration options: - -```swift -import FirebaseAppleSwiftUI -import FirebaseAuthSwiftUI -import FirebaseFacebookSwiftUI -import FirebaseGoogleSwiftUI -import FirebaseOAuthSwiftUI -import FirebasePhoneAuthSwiftUI -import FirebaseTwitterSwiftUI -import SwiftUI - -struct ContentView: View { - let authService: AuthService - - init() { - // Configure email link sign-in - let actionCodeSettings = ActionCodeSettings() - actionCodeSettings.handleCodeInApp = true - actionCodeSettings.url = URL(string: "https://yourapp.firebaseapp.com") - actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!) - - // Create configuration with options - let configuration = AuthConfiguration( - shouldAutoUpgradeAnonymousUsers: true, - tosUrl: URL(string: "https://example.com/tos"), - privacyPolicyUrl: URL(string: "https://example.com/privacy"), - emailLinkSignInActionCodeSettings: actionCodeSettings, - mfaEnabled: true - ) - - // Initialize AuthService with multiple providers - authService = AuthService(configuration: configuration) - .withEmailSignIn() - .withAppleSignIn() - .withGoogleSignIn() - .withFacebookSignIn(FacebookProviderSwift()) - .withPhoneSignIn() - .withTwitterSignIn() - .withOAuthSignIn(OAuthProviderSwift.github()) - .withOAuthSignIn(OAuthProviderSwift.microsoft()) - .withOAuthSignIn(OAuthProviderSwift.yahoo()) - } - - 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) - } - } - } -} -``` - -### How Default Views Work - -When you use `AuthPickerView`, you get: - -1. **Sheet Presentation**: Authentication UI appears as a modal sheet -2. **Built-in Navigation**: Automatic navigation between sign-in, password recovery, MFA, email link, and phone verification screens -3. **Authentication State Management**: Automatically switches between auth UI and your content based on `authService.authenticationState` -4. **Control via `isPresented`**: Control when the auth sheet appears by setting `authService.isPresented = true/false` - -### Opinionated Behaviors in Default Views - -The default `AuthPickerView` handles several complex scenarios automatically: - -#### 1. **Account Conflict Resolution** - -When an account conflict occurs (e.g., signing in with a credential that's already linked to another account), `AuthPickerView` automatically handles it: - -- **Anonymous Upgrade Conflicts**: If `shouldAutoUpgradeAnonymousUsers` is enabled and a conflict occurs during anonymous upgrade, the system automatically signs out the anonymous user and signs in with the new credential. -- **Other Conflicts**: For credential conflicts between non-anonymous accounts, the system stores the pending credential and attempts to link it after successful sign-in. - -This is handled by the `AccountConflictModifier` applied at the NavigationStack level. - -#### 2. **Multi-Factor Authentication (MFA)** - -When MFA is enabled in your configuration: - -- Automatically detects when MFA is required during sign-in -- Presents appropriate MFA resolution screens (SMS or TOTP) -- Handles MFA enrollment and management flows -- Supports both SMS-based and Time-based One-Time Password (TOTP) factors - -#### 3. **Error Handling** - -The default views include built-in error handling: - -- Displays user-friendly error messages in alert dialogs -- Automatically filters out errors that are handled internally (e.g., cancellation errors, auto-handled conflicts) -- Uses localized error messages via `StringUtils` -- Errors are propagated through the `reportError` environment key - -#### 4. **Email Link Sign-In** - -When email link sign-in is configured: - -- Automatically stores the email address in app storage -- Handles deep link navigation from email -- Manages the complete email verification flow -- Supports anonymous user upgrades via email link - -#### 5. **Anonymous User Auto-Upgrade** - -When `shouldAutoUpgradeAnonymousUsers` is enabled: - -- Automatically attempts to link anonymous accounts with new sign-in credentials -- Preserves user data by upgrading instead of replacing anonymous sessions -- Handles upgrade conflicts gracefully - -### Available Auth Methods in Default Views - -The default views support: - -- **Email/Password Authentication** (built into `FirebaseAuthSwiftUI`) -- **Email Link Authentication** (passwordless) -- **Phone Authentication** (SMS verification) -- **Sign in with Apple** -- **Sign in with Google** -- **Sign in with Facebook** (Classic and Limited Login) -- **Sign in with Twitter** -- **Generic OAuth Providers** (GitHub, Microsoft, Yahoo, or custom OIDC) - ---- - -## Usage with Custom Views - -If you need more control over the UI or navigation flow, you can build your own custom authentication views while still leveraging the `AuthService` for authentication logic. - -### Approach 1: Custom Buttons with `registerProvider()` - -For complete control over button appearance, you can create your own custom `AuthProviderUI` implementation that wraps any provider and returns your custom button view. - -#### Creating a Custom Provider UI - -Here's how to create a custom Twitter button as an example: - -```swift -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") - } - } - } - } -} -``` - -#### Simplified Custom Button Example - -You can also create simpler custom buttons for any provider: - -```swift -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) - } -} -``` - -This approach works for all providers: Google, Apple, Twitter, Facebook, Phone, and OAuth providers. Simply create your custom button view and wrap it in a class conforming to `AuthProviderUI`. - -### Approach 2: Default Buttons with Custom Views - -You can use `AuthService.renderButtons()` and bypass `AuthPickerView` to render the default authentication buttons while providing your own layout and navigation: - -```swift -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) - } -} -``` - -### Approach 3: Custom Views with Custom Navigation - -For complete control over the entire flow, you can bypass `AuthPickerView` and build your own navigation system: - -```swift -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) - } - } -} -``` - -### Important Considerations for Custom Views - -When building custom views, you need to handle several things yourself that `AuthPickerView` handles automatically: - -1. **Account Conflicts**: Implement your own conflict resolution strategy using `AuthServiceError.accountConflict` -2. **MFA Handling**: Check `SignInOutcome` for `.mfaRequired` and handle MFA resolution manually -3. **Anonymous User Upgrades**: Handle the linking of anonymous accounts if `shouldAutoUpgradeAnonymousUsers` is enabled -4. **Navigation State**: Manage navigation between different auth screens (phone verification, password recovery, etc.) -5. **Loading States**: Show loading indicators during async authentication operations by observing `authService.authenticationState` - -### Custom OAuth Providers - -You can create custom OAuth providers for services beyond the built-in ones: - -> **⚠️ Important:** OIDC (OpenID Connect) providers must be configured in your Firebase project's Authentication settings before they can be used. In the Firebase Console, go to **Authentication → Sign-in method** and add your OIDC provider with the required credentials (Client ID, Client Secret, Issuer URL). You must also register the OAuth redirect URI provided by Firebase in your provider's developer console. See the [Firebase OIDC documentation](https://firebase.google.com/docs/auth/ios/openid-connect) for detailed setup instructions. - -```swift -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) - } -} -``` - ---- - -## API Reference - -### AuthConfiguration - -The `AuthConfiguration` struct allows you to customize the behavior of the authentication flow. - -```swift -public struct AuthConfiguration { - public init( - logo: ImageResource? = nil, - languageCode: String? = nil, - shouldHideCancelButton: Bool = false, - interactiveDismissEnabled: Bool = true, - shouldAutoUpgradeAnonymousUsers: Bool = false, - customStringsBundle: Bundle? = nil, - tosUrl: URL? = nil, - privacyPolicyUrl: URL? = nil, - emailLinkSignInActionCodeSettings: ActionCodeSettings? = nil, - verifyEmailActionCodeSettings: ActionCodeSettings? = nil, - mfaEnabled: Bool = false, - allowedSecondFactors: Set = [.sms, .totp], - mfaIssuer: String = "Firebase Auth" - ) -} -``` - -#### Parameters - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `logo` | `ImageResource?` | `nil` | Custom logo to display in the authentication UI. If not provided, the default Firebase logo is used. | -| `languageCode` | `String?` | `nil` | Language code for localized strings (e.g., "en", "es", "fr"). If not provided, uses system language. | -| `shouldHideCancelButton` | `Bool` | `false` | When `true`, hides the cancel button in auth flows, preventing users from dismissing the UI. Useful for mandatory authentication. | -| `interactiveDismissEnabled` | `Bool` | `true` | When `false`, prevents users from dismissing auth sheets by swiping down. | -| `shouldAutoUpgradeAnonymousUsers` | `Bool` | `false` | When `true`, automatically links anonymous user accounts with new sign-in credentials, preserving any data associated with the anonymous session. | -| `customStringsBundle` | `Bundle?` | `nil` | Custom bundle for string localizations. Allows you to override default strings with your own translations. | -| `tosUrl` | `URL?` | `nil` | URL to your Terms of Service. When both `tosUrl` and `privacyPolicyUrl` are set, links are displayed in the auth UI. | -| `privacyPolicyUrl` | `URL?` | `nil` | URL to your Privacy Policy. When both `tosUrl` and `privacyPolicyUrl` are set, links are displayed in the auth UI. | -| `emailLinkSignInActionCodeSettings` | `ActionCodeSettings?` | `nil` | Configuration for email link (passwordless) sign-in. Must be set to use email link authentication. | -| `verifyEmailActionCodeSettings` | `ActionCodeSettings?` | `nil` | Configuration for email verification. Used when sending verification emails to users. | -| `mfaEnabled` | `Bool` | `false` | Enables Multi-Factor Authentication support. When enabled, users can enroll in and use MFA. | -| `allowedSecondFactors` | `Set` | `[.sms, .totp]` | Set of allowed MFA factor types. Options are `.sms` (phone-based) and `.totp` (authenticator app). | -| `mfaIssuer` | `String` | `"Firebase Auth"` | The issuer name displayed in TOTP authenticator apps when users enroll. | - -#### Notes - -- Both `tosUrl` and `privacyPolicyUrl` must be set for the links to appear in the UI. -- `emailLinkSignInActionCodeSettings` is **required** if you want to use email link sign-in. The `ActionCodeSettings` must have: - - `handleCodeInApp = true` - - A valid `url` - - iOS bundle ID configured via `setIOSBundleID()` - ---- - -### AuthService - -The main service class that manages authentication state and operations. - -#### Initialization - -```swift -public init( - configuration: AuthConfiguration = AuthConfiguration(), - auth: Auth = Auth.auth() -) -``` - -Creates a new `AuthService` instance. - -**Parameters:** -- `configuration`: Configuration for auth behavior (default: `AuthConfiguration()`) -- `auth`: Firebase Auth instance to use (default: `Auth.auth()`) - ---- - -#### Configuring Providers - -##### Email Authentication - -```swift -public func withEmailSignIn() -> AuthService -``` - -Enables email/password and email link authentication. Uses default behavior (navigates to email link view when tapped). - -```swift -public func withEmailSignIn(onTap: @escaping @MainActor () -> Void) -> AuthService -``` - -Enables email authentication with a custom callback when the email button is tapped. - -**Example:** - -```swift -authService - .withEmailSignIn() -``` - ---- - -##### Phone Authentication - -```swift -// Available when importing FirebasePhoneAuthSwiftUI -public func withPhoneSignIn() -> AuthService -``` - -Enables phone number authentication with SMS verification. - -**Example:** - -```swift -authService - .withPhoneSignIn() -``` - ---- - -##### Sign in with Apple - -```swift -// Available when importing FirebaseAppleSwiftUI -public func withAppleSignIn() -> AuthService -``` - -Enables Sign in with Apple authentication. - -**Example:** - -```swift -authService - .withAppleSignIn() -``` - ---- - -##### Sign in with Google - -```swift -// Available when importing FirebaseGoogleSwiftUI -public func withGoogleSignIn() -> AuthService -``` - -Enables Sign in with Google authentication. - -**Example:** - -```swift -authService - .withGoogleSignIn() -``` - ---- - -##### Sign in with Facebook - -```swift -// Available when importing FirebaseFacebookSwiftUI -public func withFacebookSignIn(_ provider: FacebookProviderSwift) -> AuthService -``` - -Enables Sign in with Facebook authentication. - -**Parameters:** -- `provider`: An instance of `FacebookProviderSwift()` for classic login or `FacebookProviderSwift(useClassicLogin: false)` for limited login. - -**Example:** - -```swift -authService - .withFacebookSignIn(FacebookProviderSwift()) -``` - ---- - -##### Sign in with Twitter - -```swift -// Available when importing FirebaseTwitterSwiftUI -public func withTwitterSignIn() -> AuthService -``` - -Enables Sign in with Twitter authentication. - -**Example:** - -```swift -authService - .withTwitterSignIn() -``` - ---- - -##### Generic OAuth Providers - -```swift -// Available when importing FirebaseOAuthSwiftUI -public func withOAuthSignIn(_ provider: OAuthProviderSwift) -> AuthService -``` - -Enables authentication with generic OAuth/OIDC providers. - -**Built-in Providers:** -- `OAuthProviderSwift.github()` -- `OAuthProviderSwift.microsoft()` -- `OAuthProviderSwift.yahoo()` - -**Custom Provider:** - -```swift -OAuthProviderSwift( - providerId: String, - displayName: String, - buttonIcon: Image, - buttonBackgroundColor: Color, - buttonForegroundColor: Color -) -``` - -**Example:** - -```swift -authService - .withOAuthSignIn(OAuthProviderSwift.github()) - .withOAuthSignIn(OAuthProviderSwift.microsoft()) - .withOAuthSignIn( - OAuthProviderSwift( - providerId: "oidc.custom-provider", - displayName: "Sign in with Custom", - buttonIcon: Image("custom-logo"), - buttonBackgroundColor: .blue, - buttonForegroundColor: .white - ) - ) -``` - ---- - -#### Custom Provider Registration - -```swift -public func registerProvider(providerWithButton: AuthProviderUI) -``` - -Registers a custom authentication provider that conforms to `AuthProviderUI`. - -**Parameters:** -- `providerWithButton`: A custom provider implementing the `AuthProviderUI` protocol. - -**Example:** - -```swift -let customProvider = MyCustomProvider() -authService.registerProvider(providerWithButton: customProvider) -``` - ---- - -#### Rendering Authentication Buttons - -```swift -public func renderButtons(spacing: CGFloat = 16) -> AnyView -``` - -Renders all registered authentication provider buttons as a vertical stack. - -**Parameters:** -- `spacing`: Vertical spacing between buttons (default: 16) - -**Returns:** An `AnyView` containing all auth buttons. - -**Example:** - -```swift -VStack { - Text("Choose a sign-in method") - authService.renderButtons(spacing: 12) -} -``` - ---- - -#### Authentication Operations - -##### Sign In with Credential - -```swift -public func signIn(_ provider: CredentialAuthProviderSwift) async throws -> SignInOutcome -``` - -Signs in using a provider that conforms to `CredentialAuthProviderSwift`. - -**Parameters:** -- `provider`: The authentication provider to use. - -**Returns:** `SignInOutcome` - either `.signedIn(AuthDataResult?)` or `.mfaRequired(MFARequired)` - -**Throws:** `AuthServiceError` or Firebase Auth errors - -**Example:** - -```swift -Task { - do { - let outcome = try await authService.signIn(GoogleProviderSwift()) - switch outcome { - case .signedIn(let result): - print("Signed in: \(result?.user.email ?? "")") - case .mfaRequired(let mfaInfo): - // Handle MFA resolution - print("MFA required: \(mfaInfo)") - } - } catch { - print("Sign in error: \(error)") - } -} -``` - ---- - -##### Sign In with Email/Password - -```swift -public func signIn(email: String, password: String) async throws -> SignInOutcome -``` - -Signs in using email and password credentials. - -**Parameters:** -- `email`: User's email address -- `password`: User's password - -**Returns:** `SignInOutcome` - -**Throws:** `AuthServiceError` or Firebase Auth errors - ---- - -##### Create User with Email/Password - -```swift -public func createUser(email: String, password: String) async throws -> SignInOutcome -``` - -Creates a new user account with email and password. - -**Parameters:** -- `email`: New user's email address -- `password`: New user's password - -**Returns:** `SignInOutcome` - -**Throws:** `AuthServiceError` or Firebase Auth errors - ---- - -##### Sign Out - -```swift -public func signOut() async throws -``` - -Signs out the current user. - -**Throws:** Firebase Auth errors - -**Example:** - -```swift -Button("Sign Out") { - Task { - try await authService.signOut() - } -} -``` - ---- - -##### Link Accounts - -```swift -public func linkAccounts(credentials: AuthCredential) async throws -``` - -Links a new authentication method to the current user's account. - -**Parameters:** -- `credentials`: The credential to link - -**Throws:** `AuthServiceError` or Firebase Auth errors - ---- - -#### Email Link (Passwordless) Authentication - -##### Send Email Sign-In Link - -```swift -public func sendEmailSignInLink(email: String) async throws -``` - -Sends a sign-in link to the specified email address. - -**Parameters:** -- `email`: Email address to send the link to - -**Throws:** `AuthServiceError` or Firebase Auth errors - -**Requirements:** `emailLinkSignInActionCodeSettings` must be configured in `AuthConfiguration` - ---- - -##### Handle Sign-In Link - -```swift -public func handleSignInLink(url: URL) async throws -``` - -Handles the sign-in flow when the user taps the email link. - -**Parameters:** -- `url`: The deep link URL from the email - -**Throws:** `AuthServiceError` or Firebase Auth errors - ---- - -#### Phone Authentication - -##### Verify Phone Number - -```swift -public func verifyPhoneNumber(phoneNumber: String) async throws -> String -``` - -Sends a verification code to the specified phone number. - -**Parameters:** -- `phoneNumber`: Phone number in E.164 format (e.g., "+15551234567") - -**Returns:** Verification ID to use when verifying the code - -**Throws:** Firebase Auth errors - ---- - -##### Sign In with Phone Number - -```swift -public func signInWithPhoneNumber( - verificationID: String, - verificationCode: String -) async throws -``` - -Signs in using a phone number and verification code. - -**Parameters:** -- `verificationID`: The verification ID returned from `verifyPhoneNumber()` -- `verificationCode`: The SMS code received by the user - -**Throws:** `AuthServiceError` or Firebase Auth errors - ---- - -#### User Profile Management - -##### Update Display Name - -```swift -public func updateUserDisplayName(name: String) async throws -``` - -Updates the current user's display name. - -**Parameters:** -- `name`: New display name - -**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors - ---- - -##### Update Photo URL - -```swift -public func updateUserPhotoURL(url: URL) async throws -``` - -Updates the current user's photo URL. - -**Parameters:** -- `url`: URL to the user's profile photo - -**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors - ---- - -##### Update Password - -```swift -public func updatePassword(to password: String) async throws -``` - -Updates the current user's password. May require recent authentication. - -**Parameters:** -- `password`: New password - -**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors - ---- - -##### Send Email Verification - -```swift -public func sendEmailVerification() async throws -``` - -Sends a verification email to the current user's email address. - -**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors - ---- - -##### Delete User - -```swift -public func deleteUser() async throws -``` - -Deletes the current user's account. May require recent authentication. - -**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors - ---- - -#### Multi-Factor Authentication (MFA) - -##### Start MFA Enrollment - -```swift -public func startMfaEnrollment( - type: SecondFactorType, - accountName: String? = nil, - issuer: String? = nil -) async throws -> EnrollmentSession -``` - -Initiates enrollment for a second factor. - -**Parameters:** -- `type`: Type of second factor (`.sms` or `.totp`) -- `accountName`: Account name for TOTP (defaults to user's email) -- `issuer`: Issuer name for TOTP (defaults to `configuration.mfaIssuer`) - -**Returns:** `EnrollmentSession` containing enrollment information - -**Throws:** `AuthServiceError` if MFA is not enabled or factor type not allowed - -**Requirements:** `mfaEnabled` must be `true` in `AuthConfiguration` - ---- - -##### Send SMS Verification for Enrollment - -```swift -public func sendSmsVerificationForEnrollment( - session: EnrollmentSession, - phoneNumber: String -) async throws -> String -``` - -Sends SMS verification code during MFA enrollment (for SMS-based second factor). - -**Parameters:** -- `session`: The enrollment session from `startMfaEnrollment()` -- `phoneNumber`: Phone number to enroll (E.164 format) - -**Returns:** Verification ID for completing enrollment - -**Throws:** `AuthServiceError` - ---- - -##### Complete MFA Enrollment - -```swift -public func completeEnrollment( - session: EnrollmentSession, - verificationId: String?, - verificationCode: String, - displayName: String -) async throws -``` - -Completes the MFA enrollment process. - -**Parameters:** -- `session`: The enrollment session -- `verificationId`: Verification ID (required for SMS, ignored for TOTP) -- `verificationCode`: The verification code from SMS or TOTP app -- `displayName`: Display name for this MFA factor - -**Throws:** `AuthServiceError` - ---- - -##### Unenroll MFA Factor - -```swift -public func unenrollMFA(_ factorUid: String) async throws -> [MultiFactorInfo] -``` - -Removes an MFA factor from the user's account. - -**Parameters:** -- `factorUid`: UID of the factor to remove - -**Returns:** Updated list of remaining enrolled factors - -**Throws:** `AuthServiceError.noCurrentUser` or Firebase Auth errors - ---- - -##### Resolve MFA Challenge (SMS) - -```swift -public func resolveSmsChallenge(hintIndex: Int) async throws -> String -``` - -Sends SMS code for resolving an MFA challenge during sign-in. - -**Parameters:** -- `hintIndex`: Index of the MFA hint to use - -**Returns:** Verification ID for completing sign-in - -**Throws:** `AuthServiceError` - ---- - -##### Resolve Sign-In with MFA - -```swift -public func resolveSignIn( - code: String, - hintIndex: Int, - verificationId: String? = nil -) async throws -``` - -Completes sign-in by verifying the MFA code. - -**Parameters:** -- `code`: The MFA code from SMS or TOTP app -- `hintIndex`: Index of the MFA hint being used -- `verificationId`: Verification ID (required for SMS, ignored for TOTP) - -**Throws:** `AuthServiceError` - ---- - -#### Public Properties - -```swift -public let configuration: AuthConfiguration -``` -The configuration used by this service. - ---- - -```swift -public let auth: Auth -``` -The Firebase Auth instance. - ---- - -```swift -public var isPresented: Bool -``` -Controls whether the authentication sheet is presented (when using `AuthPickerView`). - ---- - -```swift -public var currentUser: User? -``` -The currently signed-in Firebase user, or `nil` if not authenticated. - ---- - -```swift -public var authenticationState: AuthenticationState -``` -Current authentication state: `.unauthenticated`, `.authenticating`, or `.authenticated`. - ---- - -```swift -public var authenticationFlow: AuthenticationFlow -``` -Current flow type: `.signIn` or `.signUp`. - ---- - -```swift -public private(set) var navigator: Navigator -``` -Navigator for managing navigation routes in default views. - ---- - -```swift -public var authView: AuthView? -``` -Currently displayed auth view (e.g., `.emailLink`, `.mfaResolution`). - ---- - -### AuthPickerView - -A pre-built view that provides complete authentication UI. - -```swift -public struct AuthPickerView: View { - public init(@ViewBuilder content: @escaping () -> Content = { EmptyView() }) -} -``` - -**Parameters:** -- `content`: Your app's authenticated content, shown when user is signed in. - -**Usage:** - -```swift -AuthPickerView { - // Your app content here - Text("Welcome!") -} -.environment(authService) -``` - -**Behavior:** -- Presents authentication UI as a modal sheet controlled by `authService.isPresented` -- Automatically handles navigation between auth screens -- Includes built-in error handling and account conflict resolution -- Supports MFA flows automatically - ---- - -### Enums and Types - -#### AuthenticationState - -```swift -public enum AuthenticationState { - case unauthenticated - case authenticating - case authenticated -} -``` - -Represents the current authentication state. - ---- - -#### SignInOutcome - -```swift -public enum SignInOutcome { - case mfaRequired(MFARequired) - case signedIn(AuthDataResult?) -} -``` - -Result of a sign-in attempt. Either successful or requiring MFA. - ---- - -#### SecondFactorType - -```swift -public enum SecondFactorType { - case sms - case totp -} -``` - -Types of second factors for MFA. - ---- - -#### AuthServiceError - -```swift -public enum AuthServiceError: Error { - case noCurrentUser - case notConfiguredActionCodeSettings(String) - case invalidEmailLink(String) - case providerNotFound(String) - case invalidCredentials(String) - case multiFactorAuth(String) - case reauthenticationRequired(String) - case accountConflict(AccountConflictContext) -} -``` - -Errors specific to `AuthService` operations. - ---- - -### Best Practices - -1. **Initialize AuthService in the parent view**: Create `AuthService` once and pass it down via the environment. - -2. **Handle MFA outcomes**: Always check for `.mfaRequired` when calling sign-in methods if MFA is enabled. - -3. **Use ActionCodeSettings for email link**: Email link sign-in requires proper `ActionCodeSettings` configuration. - -4. **Test with anonymous users**: If using `shouldAutoUpgradeAnonymousUsers`, test the upgrade flow thoroughly. - -5. **Observe authentication state**: Use `onChange(of: authService.authenticationState)` to react to authentication changes. - -6. **Provider-specific setup**: Some providers (Google, Facebook) require additional configuration in AppDelegate or Info.plist. See the [sample app](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui) for examples. - ---- - -## Additional Resources - -- [Sample SwiftUI App](https://github.com/firebase/FirebaseUI-iOS/tree/main/samples/swiftui/FirebaseSwiftUIExample) -- [Firebase iOS Setup Guide](https://firebase.google.com/docs/ios/setup) -- [Firebase Authentication Documentation](https://firebase.google.com/docs/auth) -- [FirebaseUI-iOS GitHub Repository](https://github.com/firebase/FirebaseUI-iOS) - ---- - -## Feedback - -Please file feedback and issues in the [repository's issue tracker](https://github.com/firebase/FirebaseUI-iOS/issues). - From b92376c1e3fced1a01c6578e069c45d97797d71a Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 10:01:49 +0000 Subject: [PATCH 12/20] chore: shouldn't mark a specific version --- FirebaseSwiftUI/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index b17f496a18..092a2d8f68 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -17,7 +17,7 @@ 1. Launch Xcode and open the project or workspace where you want to add FirebaseUI for SwiftUI. 2. In the menu bar, go to: **File > Add Package Dependencies...** 3. Enter the Package URL: `https://github.com/firebase/FirebaseUI-iOS` -4. In the **Dependency Rule** dropdown, select **Exact Version** and set the version to `15.1.0-alpha` in the resulting text input. +4. In the **Dependency Rule** dropdown, select **Exact Version** and set the version to the latest in the resulting text input. 5. Select the targets you wish to add to your app. The available SwiftUI packages are: - **FirebaseAuthSwiftUI** (required - includes Email auth provider) - **FirebaseAppleSwiftUI** (Sign in with Apple) From 242c15b33c6a14d645eaa203b5545c158561394c Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 10:03:05 +0000 Subject: [PATCH 13/20] chore: rm facebook provider from example --- FirebaseSwiftUI/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 092a2d8f68..117d928d0e 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -143,7 +143,7 @@ struct ContentView: View { .withEmailSignIn() .withAppleSignIn() .withGoogleSignIn() - .withFacebookSignIn(FacebookProviderSwift()) + .withFacebookSignIn() .withPhoneSignIn() .withTwitterSignIn() .withOAuthSignIn(OAuthProviderSwift.github()) From 82c496ec66abf163720d61dd913012479593ae6d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 10:16:34 +0000 Subject: [PATCH 14/20] docs: ATT note and update withEmailSignIn() reference --- FirebaseSwiftUI/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 117d928d0e..91a18dd3b4 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -262,7 +262,7 @@ The default views support: - **Phone Authentication** (SMS verification) - **Sign in with Apple** - **Sign in with Google** -- **Sign in with Facebook** (Classic and Limited Login) +- **Sign in with Facebook** (Classic and Limited Login depending on whether App Tracking Transparency is authorized) - **Sign in with Twitter** - **Generic OAuth Providers** (GitHub, Microsoft, Yahoo, or custom OIDC) @@ -772,17 +772,26 @@ public func withEmailSignIn() -> AuthService Enables email/password and email link authentication. Uses default behavior (navigates to email link view when tapped). +**Example:** + ```swift -public func withEmailSignIn(onTap: @escaping @MainActor () -> Void) -> AuthService +authService + .withEmailSignIn() ``` -Enables email authentication with a custom callback when the email button is tapped. +```swift +public func withEmailSignIn(onTap: @escaping () -> Void) -> AuthService +``` + +Enables email authentication with a custom callback (i.e where to navigate when tapped) when the email button is tapped. **Example:** ```swift authService - .withEmailSignIn() + .withEmailSignIn(){ + // navigate to email sign-in screen logic + } ``` --- From 10e76f12ce32fee26805c36a324c535cf81eacb0 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 10:20:25 +0000 Subject: [PATCH 15/20] doc: withPhoneSignIn() callback --- FirebaseSwiftUI/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 91a18dd3b4..1c814b6b04 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -803,7 +803,7 @@ authService public func withPhoneSignIn() -> AuthService ``` -Enables phone number authentication with SMS verification. +Enables phone number authentication with SMS verification. Uses default behavior (navigates to enter phone number view when tapped). **Example:** @@ -812,6 +812,21 @@ authService .withPhoneSignIn() ``` +```swift +public func withPhoneSignIn(onTap: @escaping () -> Void) -> AuthService +``` + +Enables phone authentication with a custom callback (i.e where to navigate when tapped) when the phone button is tapped. + +**Example:** + +```swift +authService + .withPhoneSignIn(){ + // navigate to phone sign-in screen logic + } +``` + --- ##### Sign in with Apple From 9da1d5d401c560b8147b8e2141aa89019165243d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 10:24:16 +0000 Subject: [PATCH 16/20] docs: api reference regarding buttons being rendered --- FirebaseSwiftUI/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 1c814b6b04..8084426817 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -770,7 +770,7 @@ Creates a new `AuthService` instance. public func withEmailSignIn() -> AuthService ``` -Enables email/password and email link authentication. Uses default behavior (navigates to email link view when tapped). +Enables email/password and email link authentication and will register an email button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses default behavior (navigates to email link view when tapped). **Example:** @@ -783,7 +783,7 @@ authService public func withEmailSignIn(onTap: @escaping () -> Void) -> AuthService ``` -Enables email authentication with a custom callback (i.e where to navigate when tapped) when the email button is tapped. +Enables email authentication and will register an email button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses a custom callback (i.e where to navigate when tapped) when the email button is tapped. **Example:** @@ -803,7 +803,7 @@ authService public func withPhoneSignIn() -> AuthService ``` -Enables phone number authentication with SMS verification. Uses default behavior (navigates to enter phone number view when tapped). +Enables phone number authentication with SMS verification and will register a phone button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses default behavior (navigates to enter phone number view when tapped). **Example:** @@ -816,7 +816,7 @@ authService public func withPhoneSignIn(onTap: @escaping () -> Void) -> AuthService ``` -Enables phone authentication with a custom callback (i.e where to navigate when tapped) when the phone button is tapped. +Enables phone authentication and will register a phone button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses a custom callback (i.e where to navigate when tapped) when the phone button is tapped. **Example:** @@ -836,7 +836,7 @@ authService public func withAppleSignIn() -> AuthService ``` -Enables Sign in with Apple authentication. +Enables Sign in with Apple authentication and will register an apple button that is rendered in `AuthPickerView` (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. **Example:** @@ -854,7 +854,7 @@ authService public func withGoogleSignIn() -> AuthService ``` -Enables Sign in with Google authentication. +Enables Sign in with Google authentication and will register a Google button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. **Example:** @@ -872,7 +872,7 @@ authService public func withFacebookSignIn(_ provider: FacebookProviderSwift) -> AuthService ``` -Enables Sign in with Facebook authentication. +Enables Sign in with Facebook authentication and will register a Facebook button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. **Parameters:** - `provider`: An instance of `FacebookProviderSwift()` for classic login or `FacebookProviderSwift(useClassicLogin: false)` for limited login. @@ -893,7 +893,7 @@ authService public func withTwitterSignIn() -> AuthService ``` -Enables Sign in with Twitter authentication. +Enables Sign in with Twitter authentication and will register a Twitter button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. **Example:** @@ -911,7 +911,7 @@ authService public func withOAuthSignIn(_ provider: OAuthProviderSwift) -> AuthService ``` -Enables authentication with generic OAuth/OIDC providers. +Enables authentication with generic OAuth/OIDC providers and will register an OAuth button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. **Built-in Providers:** - `OAuthProviderSwift.github()` From bd652614c7ee88840cfa65ae29fb6b4148c89810 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 10:37:20 +0000 Subject: [PATCH 17/20] docs: update provider API reference --- FirebaseSwiftUI/README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 8084426817..8c8589b2b9 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -833,11 +833,14 @@ authService ```swift // Available when importing FirebaseAppleSwiftUI -public func withAppleSignIn() -> AuthService +public func withAppleSignIn(_ provider: AppleProviderSwift? = nil) -> AuthService ``` Enables Sign in with Apple authentication and will register an apple button that is rendered in `AuthPickerView` (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. +**Parameters:** +- `provider`: An optional instance of `AppleProviderSwift`. If not provided, a default instance will be created. + **Example:** ```swift @@ -851,11 +854,14 @@ authService ```swift // Available when importing FirebaseGoogleSwiftUI -public func withGoogleSignIn() -> AuthService +public func withGoogleSignIn(_ provider: GoogleProviderSwift? = nil) -> AuthService ``` Enables Sign in with Google authentication and will register a Google button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. +**Parameters:** +- `provider`: An optional instance of `GoogleProviderSwift`. If not provided, a default instance will be created using the client ID from Firebase configuration. + **Example:** ```swift @@ -890,11 +896,14 @@ authService ```swift // Available when importing FirebaseTwitterSwiftUI -public func withTwitterSignIn() -> AuthService +public func withTwitterSignIn(_ provider: TwitterProviderSwift? = nil) -> AuthService ``` Enables Sign in with Twitter authentication and will register a Twitter button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. +**Parameters:** +- `provider`: An optional instance of `TwitterProviderSwift`. If not provided, a default instance will be created. + **Example:** ```swift From 7926c5d5d784a9d09ceddcac9e8897a692fbf02a Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 16:01:37 +0000 Subject: [PATCH 18/20] docs: update email and phone auth sign in --- FirebaseSwiftUI/README.md | 50 ++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 8c8589b2b9..bc1ee58ac9 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -767,27 +767,23 @@ Creates a new `AuthService` instance. ##### Email Authentication ```swift -public func withEmailSignIn() -> AuthService +public func withEmailSignIn(_ provider: EmailProviderSwift? = nil, onTap: @escaping () -> Void) -> AuthService ``` -Enables email/password and email link authentication and will register an email button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses default behavior (navigates to email link view when tapped). +Enables email authentication and will render email sign-in directly within the AuthPickerView (default Views), email link sign-in is rendered as a button. When calling `AuthService.renderButtons()`, email link sign-in button is rendered. `onTap` custom callback (i.e where to navigate when tapped) allows user to control what happens when tapped. Default behavior in AuthPickerView is to push the user to email link sign-in default View. + +**Parameters:** +- `provider`: An optional instance of `EmailProviderSwift`. If not provided, a default instance will be created. +- `onTap`: A callback that will be executed when the email button is tapped. **Example:** ```swift authService .withEmailSignIn() -``` - -```swift -public func withEmailSignIn(onTap: @escaping () -> Void) -> AuthService -``` - -Enables email authentication and will register an email button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses a custom callback (i.e where to navigate when tapped) when the email button is tapped. -**Example:** +// or -```swift authService .withEmailSignIn(){ // navigate to email sign-in screen logic @@ -799,30 +795,24 @@ authService ##### Phone Authentication ```swift -// Available when importing FirebasePhoneAuthSwiftUI -public func withPhoneSignIn() -> AuthService +public func withPhoneSignIn(onTap: @escaping () -> Void) -> AuthService ``` -Enables phone number authentication with SMS verification and will register a phone button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses default behavior (navigates to enter phone number view when tapped). +Enables phone number authentication with SMS verification and will register a phone button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. `onTap` custom callback (i.e where to navigate when tapped) allows user to control what happens when tapped. Default behavior in AuthPickerView is to push the user to phone sign-in default View. + +**Parameters:** +- `onTap`: A callback that will be executed when the phone button is tapped. **Example:** ```swift authService .withPhoneSignIn() -``` - -```swift -public func withPhoneSignIn(onTap: @escaping () -> Void) -> AuthService -``` -Enables phone authentication and will register a phone button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. Uses a custom callback (i.e where to navigate when tapped) when the phone button is tapped. +// or -**Example:** - -```swift authService - .withPhoneSignIn(){ + .withPhoneSignIn() { // navigate to phone sign-in screen logic } ``` @@ -875,19 +865,19 @@ authService ```swift // Available when importing FirebaseFacebookSwiftUI -public func withFacebookSignIn(_ provider: FacebookProviderSwift) -> AuthService +public func withFacebookSignIn(_ provider: FacebookProviderSwift? = nil) -> AuthService ``` Enables Sign in with Facebook authentication and will register a Facebook button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. **Parameters:** -- `provider`: An instance of `FacebookProviderSwift()` for classic login or `FacebookProviderSwift(useClassicLogin: false)` for limited login. +- `provider`: An optional instance of `FacebookProviderSwift()` for classic login or `FacebookProviderSwift(useClassicLogin: false)` for limited login. If not provided, a default instance with classic login will be created. **Example:** ```swift authService - .withFacebookSignIn(FacebookProviderSwift()) + .withFacebookSignIn() ``` --- @@ -1061,7 +1051,7 @@ Signs in using email and password credentials. ##### Create User with Email/Password ```swift -public func createUser(email: String, password: String) async throws -> SignInOutcome +public func createUser(email email: String, password: String) async throws -> SignInOutcome ``` Creates a new user account with email and password. @@ -1101,7 +1091,7 @@ Button("Sign Out") { ##### Link Accounts ```swift -public func linkAccounts(credentials: AuthCredential) async throws +public func linkAccounts(credentials credentials: AuthCredential) async throws ``` Links a new authentication method to the current user's account. @@ -1135,7 +1125,7 @@ Sends a sign-in link to the specified email address. ##### Handle Sign-In Link ```swift -public func handleSignInLink(url: URL) async throws +public func handleSignInLink(url url: URL) async throws ``` Handles the sign-in flow when the user taps the email link. From 71654b35740350957ee371f33e40502c8c62ca85 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 14 Nov 2025 16:04:38 +0000 Subject: [PATCH 19/20] docs: passwordPrompt --- FirebaseSwiftUI/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index bc1ee58ac9..04b89fcf37 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -1423,6 +1423,22 @@ Navigator for managing navigation routes in default views. --- +```swift +public var passwordPrompt: PasswordPromptCoordinator +``` +A coordinator that manages password prompt dialogs during reauthentication flows for the email provider. + +Users can provide a custom `PasswordPromptCoordinator` instance when initializing `EmailProviderSwift` to customize password prompting behavior: + +```swift +let customPrompt = PasswordPromptCoordinator() +authService.withEmailSignIn(EmailProviderSwift(passwordPrompt: customPrompt)) +``` + +**Default Behavior:** If no custom coordinator is provided, a default `PasswordPromptCoordinator()` instance is created automatically. The default coordinator displays a modal sheet that prompts the user to enter their password when reauthentication is required for sensitive operations (e.g., updating email, deleting account). + +--- + ```swift public var authView: AuthView? ``` From 393f9cf93df40f37eb1fab0b04a5e62144a0345a Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 17 Nov 2025 11:33:10 +0000 Subject: [PATCH 20/20] docs: format --- FirebaseSwiftUI/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseSwiftUI/README.md b/FirebaseSwiftUI/README.md index 04b89fcf37..222fafca3a 100644 --- a/FirebaseSwiftUI/README.md +++ b/FirebaseSwiftUI/README.md @@ -578,7 +578,7 @@ struct ContentView: View { Button("Sign in with Google") { Task { do { - let provider = GoogleProviderSwift( clientID: Auth.auth().app?.options.clientID ?? "") + let provider = GoogleProviderSwift(clientID: Auth.auth().app?.options.clientID ?? "") let outcome = try await authService.signIn(provider) // Handle MFA if required @@ -767,7 +767,7 @@ Creates a new `AuthService` instance. ##### Email Authentication ```swift -public func withEmailSignIn(_ provider: EmailProviderSwift? = nil, onTap: @escaping () -> Void) -> AuthService +public func withEmailSignIn(_ provider: EmailProviderSwift? = nil, onTap: @escaping () -> Void = {}) -> AuthService ``` Enables email authentication and will render email sign-in directly within the AuthPickerView (default Views), email link sign-in is rendered as a button. When calling `AuthService.renderButtons()`, email link sign-in button is rendered. `onTap` custom callback (i.e where to navigate when tapped) allows user to control what happens when tapped. Default behavior in AuthPickerView is to push the user to email link sign-in default View. @@ -785,7 +785,7 @@ authService // or authService - .withEmailSignIn(){ + .withEmailSignIn() { // navigate to email sign-in screen logic } ``` @@ -795,7 +795,7 @@ authService ##### Phone Authentication ```swift -public func withPhoneSignIn(onTap: @escaping () -> Void) -> AuthService +public func withPhoneSignIn(onTap: @escaping () -> Void = {}) -> AuthService ``` Enables phone number authentication with SMS verification and will register a phone button that is rendered in AuthPickerView (default Views) or can be rendered in custom Views by calling `AuthService.renderButtons()`. `onTap` custom callback (i.e where to navigate when tapped) allows user to control what happens when tapped. Default behavior in AuthPickerView is to push the user to phone sign-in default View.