- Launch Xcode and open the project or workspace where you want to add FirebaseUI for SwiftUI.
- In the menu bar, go to: File > Add Package Dependencies...
- Enter the Package URL:
https://github.com/firebase/FirebaseUI-iOS - In the Dependency Rule dropdown, select Exact Version and set the version to the latest in the resulting text input.
- 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)
- Press the Add Packages button to complete installation.
- Minimum iOS Version: iOS 17+
- Swift Version: Swift 6.0+
Before using FirebaseUI for SwiftUI, you need to configure Firebase in your app:
- Follow steps 2, 3 & 5 in adding Firebase to your iOS app.
- Update your app entry point:
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()
}
}
}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.
Here's a minimal example using the default views with email authentication:
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)
}
}Here's a more complete example with multiple providers and configuration options:
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()
.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)
}
}
}
}When you use AuthPickerView, you get:
- Sheet Presentation: Authentication UI appears as a modal sheet
- Built-in Navigation: Automatic navigation between sign-in, password recovery, MFA, email link, and phone verification screens
- Authentication State Management: Automatically switches between auth UI and your content based on
authService.authenticationState - Control via
isPresented: Control when the auth sheet appears by settingauthService.isPresented = true/false
The default AuthPickerView handles several complex scenarios automatically:
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
shouldAutoUpgradeAnonymousUsersis 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.
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
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
reportErrorenvironment key
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
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
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 depending on whether App Tracking Transparency is authorized)
- Sign in with Twitter
- Generic OAuth Providers (GitHub, Microsoft, Yahoo, or custom OIDC)
Sensitive operations like deleting accounts, updating passwords, or unenrolling MFA factors require recent authentication. When using default views, reauthentication is handled automatically based on the user's sign-in provider.
When a sensitive operation requires reauthentication, the default views automatically:
-
OAuth Providers (Google, Apple, Facebook, Twitter, etc.): Display an alert asking the user to confirm, then automatically obtain fresh credentials and complete the operation.
-
Email/Password: Present a sheet prompting the user to enter their password before continuing.
-
Email Link: Show an alert asking to send a verification email, then present a sheet with instructions to check email. The user taps the link in their email to complete reauthentication.
-
Phone: Show an alert explaining verification is needed, then present a sheet for SMS code verification.
The operation automatically retries after successful reauthentication. No additional code is required when using AuthPickerView or the built-in account management views (UpdatePasswordView, SignedInView, etc.).
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.
For complete control over button appearance, you can create your own custom AuthProviderUI implementation that wraps any provider and returns your custom button view.
Here's how to create a custom Twitter button as an example:
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")
}
}
}
}
}You can also create simpler custom buttons for any provider:
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.
You can use AuthService.renderButtons() and bypass AuthPickerView to render the default authentication buttons while providing your own layout and navigation:
import FirebaseAuthSwiftUI
import FirebaseGoogleSwiftUI
import FirebaseAppleSwiftUI
import SwiftUI
struct CustomAuthView: View {
@Environment(AuthService.self) private var authService
var body: some View {
VStack(spacing: 30) {
// Your custom logo/branding
Image("app-logo")
.resizable()
.frame(width: 150, height: 150)
Text("Welcome to My App")
.font(.largeTitle)
.fontWeight(.bold)
Text("Sign in to continue")
.font(.subheadline)
.foregroundStyle(.secondary)
// Render default auth buttons
authService.renderButtons(spacing: 12)
.padding()
}
.padding()
}
}
struct ContentView: View {
init() {
let configuration = AuthConfiguration()
authService = AuthService(configuration: configuration)
.withGoogleSignIn()
.withAppleSignIn()
}
let authService: AuthService
var body: some View {
NavigationStack {
if authService.authenticationState == .authenticated {
Text("Authenticated!")
} else {
CustomAuthView()
}
}
.environment(authService)
}
}For complete control over the entire flow, you can bypass AuthPickerView and build your own navigation system:
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)
}
}
}When building custom views, you need to handle several things yourself that AuthPickerView handles automatically:
- Account Conflicts: Implement your own conflict resolution strategy using
AuthServiceError.accountConflict - MFA Handling: Check
SignInOutcomefor.mfaRequiredand handle MFA resolution manually - Anonymous User Upgrades: Handle the linking of anonymous accounts if
shouldAutoUpgradeAnonymousUsersis enabled - Navigation State: Manage navigation between different auth screens (phone verification, password recovery, etc.)
- Loading States: Show loading indicators during async authentication operations by observing
authService.authenticationState - Reauthentication: Handle reauthentication errors for sensitive operations (see Reauthentication in Custom Views below)
When building custom views, handle reauthentication by catching specific errors and implementing your own flow. Sensitive operations throw four types of reauthentication errors, each containing context information.
OAuth Providers (Google, Apple, Facebook, Twitter, etc.):
Catch the error and call reauthenticate(context:) which automatically handles the OAuth flow:
do {
try await authService.deleteUser()
} catch let error as AuthServiceError {
if case .oauthReauthenticationRequired(let context) = error {
try await authService.reauthenticate(context: context)
try await authService.deleteUser() // Retry operation
}
}Email/Password:
Catch the error, prompt for password, create credential, and call reauthenticate(with:):
do {
try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
if case .emailReauthenticationRequired(let context) = error {
// Show your password prompt UI
let password = await promptUserForPassword()
let credential = EmailAuthProvider.credential(
withEmail: context.email,
password: password
)
try await authService.reauthenticate(with: credential)
try await authService.updatePassword(to: newPassword) // Retry
}
}Phone:
Catch the error, verify phone, create credential, and call reauthenticate(with:):
do {
try await authService.deleteUser()
} catch let error as AuthServiceError {
if case .phoneReauthenticationRequired(let context) = error {
// Send verification code
let verificationId = try await authService.verifyPhoneNumber(
phoneNumber: context.phoneNumber
)
// Show your SMS code input UI
let code = await promptUserForSMSCode()
let credential = PhoneAuthProvider.provider().credential(
withVerificationID: verificationId,
verificationCode: code
)
try await authService.reauthenticate(with: credential)
try await authService.deleteUser() // Retry
}
}Email Link:
Catch the error, send verification email, and handle the incoming URL:
do {
try await authService.updatePassword(to: newPassword)
} catch let error as AuthServiceError {
if case .emailLinkReauthenticationRequired(let context) = error {
// Send verification email
try await authService.sendEmailSignInLink(
email: context.email,
isReauth: true
)
// Show your "Check your email" UI
await showCheckEmailUI()
// When user taps the link, it opens your app with a URL
// Handle it in your URL handler:
// try await authService.handleSignInLink(url: url)
// The handleSignInLink method automatically completes reauthentication
try await authService.updatePassword(to: newPassword) // Retry
}
}All reauthentication context objects include a .displayMessage property for user-facing text.
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 for detailed setup instructions.
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)
}
}Override any UI string by creating a Localizable.strings (or .xcstrings) file in your app with custom values. Only include strings you want to change. Strings not changed will fallback to FirebaseAuthSwiftUI default strings.
// Localizable.strings
"Sign in with Firebase" = "Welcome Back!";
// In your app configuration
let configuration = AuthConfiguration(customStringsBundle: .main)See example implementation for a working demo.
The AuthConfiguration struct allows you to customize the behavior of the authentication flow.
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<SecondFactorType> = [.sms, .totp],
mfaIssuer: String = "Firebase Auth"
)
}| 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<SecondFactorType> |
[.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. |
- Both
tosUrlandprivacyPolicyUrlmust be set for the links to appear in the UI. emailLinkSignInActionCodeSettingsis required if you want to use email link sign-in. TheActionCodeSettingsmust have:handleCodeInApp = true- A valid
url - iOS bundle ID configured via
setIOSBundleID()
The main service class that manages authentication state and operations.
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())
public func withEmailSignIn(onTap: @escaping () -> Void = {}) -> AuthServiceEnables 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:
onTap: A callback that will be executed when the email button is tapped.
Example:
authService
.withEmailSignIn()
// or
authService
.withEmailSignIn() {
// navigate to email sign-in screen logic
}public func withPhoneSignIn(onTap: @escaping () -> Void = {}) -> AuthServiceEnables 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:
authService
.withPhoneSignIn()
// or
authService
.withPhoneSignIn() {
// navigate to phone sign-in screen logic
}// Available when importing FirebaseAppleSwiftUI
public func withAppleSignIn(_ provider: AppleProviderSwift? = nil) -> AuthServiceEnables 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 ofAppleProviderSwift. If not provided, a default instance will be created.
Example:
authService
.withAppleSignIn()// Available when importing FirebaseGoogleSwiftUI
public func withGoogleSignIn(_ provider: GoogleProviderSwift? = nil) -> AuthServiceEnables 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 ofGoogleProviderSwift. If not provided, a default instance will be created using the client ID from Firebase configuration.
Example:
authService
.withGoogleSignIn()// Available when importing FirebaseFacebookSwiftUI
public func withFacebookSignIn(_ provider: FacebookProviderSwift? = nil) -> AuthServiceEnables 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 optional instance ofFacebookProviderSwift()for classic login orFacebookProviderSwift(useClassicLogin: false)for limited login. If not provided, a default instance with classic login will be created.
Example:
authService
.withFacebookSignIn()// Available when importing FirebaseTwitterSwiftUI
public func withTwitterSignIn(_ provider: TwitterProviderSwift? = nil) -> AuthServiceEnables 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 ofTwitterProviderSwift. If not provided, a default instance will be created.
Example:
authService
.withTwitterSignIn()// Available when importing FirebaseOAuthSwiftUI
public func withOAuthSignIn(_ provider: OAuthProviderSwift) -> AuthServiceEnables 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()OAuthProviderSwift.microsoft()OAuthProviderSwift.yahoo()
Custom Provider:
OAuthProviderSwift(
providerId: String,
displayName: String,
buttonIcon: Image,
buttonBackgroundColor: Color,
buttonForegroundColor: Color
)Example:
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
)
)public func registerProvider(providerWithButton: AuthProviderUI)Registers a custom authentication provider that conforms to AuthProviderUI.
Parameters:
providerWithButton: A custom provider implementing theAuthProviderUIprotocol.
Example:
let customProvider = MyCustomProvider()
authService.registerProvider(providerWithButton: customProvider)public func renderButtons(spacing: CGFloat = 16) -> AnyViewRenders 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:
VStack {
Text("Choose a sign-in method")
authService.renderButtons(spacing: 12)
}public func signIn(_ provider: CredentialAuthProviderSwift) async throws -> SignInOutcomeSigns 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:
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)")
}
}public func signIn(email: String, password: String) async throws -> SignInOutcomeSigns in using email and password credentials.
Parameters:
email: User's email addresspassword: User's password
Returns: SignInOutcome
Throws: AuthServiceError or Firebase Auth errors
public func createUser(email email: String, password: String) async throws -> SignInOutcomeCreates a new user account with email and password.
Parameters:
email: New user's email addresspassword: New user's password
Returns: SignInOutcome
Throws: AuthServiceError or Firebase Auth errors
public func signOut() async throwsSigns out the current user.
Throws: Firebase Auth errors
Example:
Button("Sign Out") {
Task {
try await authService.signOut()
}
}public func linkAccounts(credentials credentials: AuthCredential) async throwsLinks a new authentication method to the current user's account.
Parameters:
credentials: The credential to link
Throws: AuthServiceError or Firebase Auth errors
public func sendEmailSignInLink(email: String, isReauth: Bool = false) async throwsSends a sign-in link to the specified email address. Can also be used for reauthentication.
Parameters:
email: Email address to send the link toisReauth: Whether this is for reauthentication (default:false)
Throws: AuthServiceError or Firebase Auth errors
Requirements: emailLinkSignInActionCodeSettings must be configured in AuthConfiguration
Note: When isReauth is true, the method stores the email for reauthentication flow. The same handleSignInLink(url:) method handles both sign-in and reauthentication automatically.
public func handleSignInLink(url url: URL) async throwsHandles the email link flow when the user taps the link. Automatically routes to either sign-in or reauthentication based on the current context.
Parameters:
url: The deep link URL from the email
Throws: AuthServiceError or Firebase Auth errors
Note: This method handles both initial sign-in and reauthentication flows automatically. The behavior is determined by whether sendEmailSignInLink(email:isReauth:) was called with isReauth: true.
public func verifyPhoneNumber(phoneNumber: String) async throws -> StringSends 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
public func signInWithPhoneNumber(
verificationID: String,
verificationCode: String
) async throwsSigns in using a phone number and verification code.
Parameters:
verificationID: The verification ID returned fromverifyPhoneNumber()verificationCode: The SMS code received by the user
Throws: AuthServiceError or Firebase Auth errors
public func updateUserDisplayName(name: String) async throwsUpdates the current user's display name.
Parameters:
name: New display name
Throws: AuthServiceError.noCurrentUser or Firebase Auth errors
public func updateUserPhotoURL(url: URL) async throwsUpdates the current user's photo URL.
Parameters:
url: URL to the user's profile photo
Throws: AuthServiceError.noCurrentUser or Firebase Auth errors
public func updatePassword(to password: String) async throwsUpdates the current user's password. This is a sensitive operation that may require recent authentication.
Parameters:
password: New password
Throws:
AuthServiceError.noCurrentUserif no user is signed in- Reauthentication errors (
emailReauthenticationRequired,emailLinkReauthenticationRequired,phoneReauthenticationRequired, oroauthReauthenticationRequired) if recent authentication is required - see Reauthentication - Firebase Auth errors
public func sendEmailVerification() async throwsSends a verification email to the current user's email address.
Throws: AuthServiceError.noCurrentUser or Firebase Auth errors
public func deleteUser() async throwsDeletes the current user's account. This is a sensitive operation that requires recent authentication.
Throws:
AuthServiceError.noCurrentUserif no user is signed in- Reauthentication errors (
emailReauthenticationRequired,emailLinkReauthenticationRequired,phoneReauthenticationRequired, oroauthReauthenticationRequired) if recent authentication is required - see Reauthentication - Firebase Auth errors
public func reauthenticate(context: OAuthReauthContext) async throwsReauthenticates the current user with an OAuth provider (Google, Apple, Facebook, Twitter, etc.). Automatically locates the registered provider, obtains fresh credentials, and completes reauthentication.
Parameters:
context: The reauth context fromoauthReauthenticationRequirederror
Throws: AuthServiceError.noCurrentUser or AuthServiceError.providerNotFound
Note: Only works for OAuth providers. For email/phone, use reauthenticate(with:).
public func reauthenticate(with credential: AuthCredential) async throwsReauthenticates the current user with a pre-obtained authentication credential. Use for email/password or phone authentication.
Parameters:
credential: The authentication credential (fromEmailAuthProviderorPhoneAuthProvider)
Throws: AuthServiceError.noCurrentUser or Firebase Auth errors
public func startMfaEnrollment(
type: SecondFactorType,
accountName: String? = nil,
issuer: String? = nil
) async throws -> EnrollmentSessionInitiates enrollment for a second factor.
Parameters:
type: Type of second factor (.smsor.totp)accountName: Account name for TOTP (defaults to user's email)issuer: Issuer name for TOTP (defaults toconfiguration.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
public func sendSmsVerificationForEnrollment(
session: EnrollmentSession,
phoneNumber: String
) async throws -> StringSends SMS verification code during MFA enrollment (for SMS-based second factor).
Parameters:
session: The enrollment session fromstartMfaEnrollment()phoneNumber: Phone number to enroll (E.164 format)
Returns: Verification ID for completing enrollment
Throws: AuthServiceError
public func completeEnrollment(
session: EnrollmentSession,
verificationId: String?,
verificationCode: String,
displayName: String
) async throwsCompletes the MFA enrollment process.
Parameters:
session: The enrollment sessionverificationId: Verification ID (required for SMS, ignored for TOTP)verificationCode: The verification code from SMS or TOTP appdisplayName: Display name for this MFA factor
Throws: AuthServiceError
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
public func resolveSmsChallenge(hintIndex: Int) async throws -> StringSends 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
public func resolveSignIn(
code: String,
hintIndex: Int,
verificationId: String? = nil
) async throwsCompletes sign-in by verifying the MFA code.
Parameters:
code: The MFA code from SMS or TOTP apphintIndex: Index of the MFA hint being usedverificationId: Verification ID (required for SMS, ignored for TOTP)
Throws: AuthServiceError
public let configuration: AuthConfigurationThe configuration used by this service.
public let auth: AuthThe Firebase Auth instance.
public var isPresented: BoolControls whether the authentication sheet is presented (when using AuthPickerView).
public var currentUser: User?The currently signed-in Firebase user, or nil if not authenticated.
public var authenticationState: AuthenticationStateCurrent authentication state: .unauthenticated, .authenticating, or .authenticated.
public var authenticationFlow: AuthenticationFlowCurrent flow type: .signIn or .signUp.
public private(set) var navigator: NavigatorNavigator for managing navigation routes in default views.
public var authView: AuthView?Currently displayed auth view (e.g., .emailLink, .mfaResolution).
A pre-built view that provides complete authentication UI.
public struct AuthPickerView<Content: View>: View {
public init(@ViewBuilder content: @escaping () -> Content = { EmptyView() })
}Parameters:
content: Your app's authenticated content, shown when user is signed in.
Usage:
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
public enum AuthenticationState {
case unauthenticated
case authenticating
case authenticated
}Represents the current authentication state.
public enum SignInOutcome {
case mfaRequired(MFARequired)
case signedIn(AuthDataResult?)
}Result of a sign-in attempt. Either successful or requiring MFA.
public enum SecondFactorType {
case sms
case totp
}Types of second factors for MFA.
public enum AuthServiceError: Error {
case noCurrentUser
case notConfiguredActionCodeSettings(String)
case invalidEmailLink(String)
case providerNotFound(String)
case invalidCredentials(String)
case multiFactorAuth(String)
case oauthReauthenticationRequired(context: OAuthReauthContext)
case emailReauthenticationRequired(context: EmailReauthContext)
case emailLinkReauthenticationRequired(context: EmailLinkReauthContext)
case phoneReauthenticationRequired(context: PhoneReauthContext)
case accountConflict(AccountConflictContext)
}Errors specific to AuthService operations.
Reauthentication Errors:
Thrown by sensitive operations when Firebase requires recent authentication. Each includes context information:
-
oauthReauthenticationRequired(context: OAuthReauthContext): OAuth providers. Context containsproviderId,providerName, anddisplayMessage. Pass toreauthenticate(context:). -
emailReauthenticationRequired(context: EmailReauthContext): Email/password provider. Context containsemailanddisplayMessage. Prompt for password, then callreauthenticate(with:). -
emailLinkReauthenticationRequired(context: EmailLinkReauthContext): Email link (passwordless) provider. Context containsemailanddisplayMessage. Send verification email withsendEmailSignInLink(email:isReauth:true), then handle the incoming link withhandleSignInLink(url:). -
phoneReauthenticationRequired(context: PhoneReauthContext): Phone provider. Context containsphoneNumberanddisplayMessage. Handle SMS verification, then callreauthenticate(with:).
-
Initialize AuthService in the parent view: Create
AuthServiceonce and pass it down via the environment. -
Handle MFA outcomes: Always check for
.mfaRequiredwhen calling sign-in methods if MFA is enabled. -
Use ActionCodeSettings for email link: Email link sign-in requires proper
ActionCodeSettingsconfiguration. -
Test with anonymous users: If using
shouldAutoUpgradeAnonymousUsers, test the upgrade flow thoroughly. -
Observe authentication state: Use
onChange(of: authService.authenticationState)to react to authentication changes. -
Provider-specific setup: Some providers (Google, Facebook) require additional configuration in AppDelegate or Info.plist. See the sample app for examples.
-
Handle reauthentication: Default views handle reauthentication automatically. For custom views, catch and handle reauthentication errors when performing sensitive operations like
deleteUser(),updatePassword(), andunenrollMFA(). See Reauthentication in Custom Views.
- Sample SwiftUI App
- Firebase iOS Setup Guide
- Firebase Authentication Documentation
- FirebaseUI-iOS GitHub Repository
Please file feedback and issues in the repository's issue tracker.