Building secure iOS authentication for your SwiftUI apps just got easier. This SwiftUI Firebase tutorial shows you how to implement a complete login system using Firebase Auth—Google’s robust authentication platform that’s been the gold standard for mobile app development.
Whether you’re building your first iOS app authentication system or looking to learn more about Firebase, this guide provides everything you need. You’ll learn to create secure sign-in, registration, and session management with clean, production-ready SwiftUI code.
Unlike basic authentication guides, we’ll build a complete system with proper error handling, input validation, and real-world best practices that you can deploy immediately.
What You’ll Learn
By the end of this tutorial, you’ll have a fully functional authentication system that includes:
- User registration with email verification
- Secure sign-in functionality
- Password reset capabilities
- Session management
- Error handling and user feedback
- Clean, reusable SwiftUI components
Prerequisites
Before we dive in, make sure you have:
- Xcode 14+ installed
- Basic knowledge of SwiftUI
- A Google/Firebase account (free tier available)
- iOS 15+ as your deployment target
Setting Up Your Firebase Project
First, let’s set up the backend infrastructure:
1. Create a Firebase Project
- Visit firebase.google.com and sign in with your Google account
- Click “Create a project” and follow the setup wizard
- Enter your project name and configure analytics (optional)
- Wait for your project to be created (usually takes 1-2 minutes)
- In the Firebase console, click “Add app” and select iOS
- Register your app with your bundle identifier
- Download the
GoogleService-Info.plistfile - Place the .plist file inside your project like the picture below

2. Configure Authentication Settings
To simplify this tutorial we are going to disable email verification – when you go to production, it’s good practice to keep this enabled and set it up correctly. But this tutorial is focused on implementing Firebase inside an iOS application:
- Go to Authentication > Get Started > Sign-in method
- Click on Email/Password
- Enable Email/Password authentication
- Disable “Email link (passwordless sign-in)” for now
- Save your changes
iOS Project Setup with Firebase
Before we can do anything we need to create a new App, so open Xcode and create a new iOS project with SwiftUI interface.
Implement Firebase
Now it’s time to add Firebase to our project. Firebase provides excellent iOS SDKs that we’ll integrate using Swift Package Manager.
In Xcode, go to File → Add Package Dependencies and add:
https://github.com/firebase/firebase-ios-sdkSelect these packages for your project:
- FirebaseAuth
- FirebaseCore
Then add the GoogleService-Info.plist file you downloaded earlier to your Xcode project.

Setting Up Firebase in Your SwiftUI App
When building SwiftUI apps that need Firebase services, you’ll need to initialize Firebase before your app starts running. We’ll do that by creating a AppDelegate class. Here’s the standard way to do it using an App Delegate:
import UIKit
import FirebaseCore
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
What’s happening here:
- UIKit and FirebaseCore imports – UIKit gives us access to the app delegate protocol, while FirebaseCore contains the initialization methods
- AppDelegate class creation – We create a custom app delegate that inherits from NSObject and conforms to UIApplicationDelegate
- App launch method – The
application(_:didFinishLaunchingWithOptions:)method runs automatically when your app starts - Firebase initialization –
FirebaseApp.configure()reads your Firebase configuration file and sets up the connection - Success return – Returning
truetells iOS that the app launched successfully
This App Delegate must be connected to your main SwiftUI App struct using @UIApplicationDelegateAdaptor to ensure Firebase initializes before your views load.
Setting Up the Main App Structure for Firebase Authentication
The main App struct is where everything comes together – it connects your App Delegate for Firebase initialization and launches your app’s main interface. Here’s how to structure your SwiftUI app entry point:
import SwiftUI
@main
struct FirebaseAuthTutorialApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}What’s happening here:
- App entry point –
@mainattribute marks this as the application’s main entry point where execution begins - App protocol conformance – The struct conforms to the
Appprotocol, which defines the structure of your SwiftUI app - Delegate adapter –
@UIApplicationDelegateAdaptorconnects your custom AppDelegate to the SwiftUI app lifecycle - Firebase initialization – The AppDelegate reference ensures Firebase.configure() runs before your views load
- Scene definition –
WindowGroupcreates the main window that contains your app’s user interface - Root view setup –
ContentView()becomes the first view users see when the app launches - SwiftUI lifecycle – This structure uses SwiftUI’s modern app lifecycle instead of the traditional UIKit approach
- Automatic management – SwiftUI handles window management, scene transitions, and app state changes
- Clean architecture – Separates app initialization (AppDelegate) from UI structure (App body)
This App struct serves as the foundation that ties together Firebase initialization with your SwiftUI authentication interface, ensuring everything loads in the correct order.
Creating a Firebase Authentication Manager for SwiftUI
Managing user authentication in SwiftUI apps requires a centralized system that can track login status and communicate changes to your views. Here’s a complete AuthManager class that handles all the essential authentication functions:
import Foundation
import FirebaseAuth
class AuthManager: ObservableObject {
@Published var isSignedIn: Bool = false
@Published var user: User?
init() {
checkAuthStatus()
}
func checkAuthStatus() {
if Auth.auth().currentUser != nil {
isSignedIn = true
user = Auth.auth().currentUser
}else {
isSignedIn = false
user = nil
}
}
@MainActor
func createUser(email: String, password: String) async throws {
do {
let result = try await Auth.auth().createUser(withEmail: email, password: password)
self.isSignedIn = true
self.user = result.user
} catch {
self.isSignedIn = false
throw error
}
}
@MainActor
func signIn(email: String, password: String) async throws {
do {
let result = try await Auth.auth().signIn(withEmail: email, password: password)
self.isSignedIn = true
self.user = result.user
} catch {
self.isSignedIn = false
throw error
}
}
func signOut() throws {
do {
try Auth.auth().signOut()
self.isSignedIn = false
self.user = nil
} catch {
throw error
}
}
func forgotPassword(email: String) throws {
Auth.auth().sendPasswordReset(withEmail: email)
}
}
What’s happening here:
- ObservableObject setup – The class publishes authentication state changes to update SwiftUI views automatically
- State tracking –
isSignedInanduserproperties maintain current authentication status - Automatic status check – Constructor calls
checkAuthStatus()to detect existing user sessions - User registration –
createUser()creates new Firebase accounts and updates local state on success - Sign in functionality –
signIn()authenticates users and stores their information locally - Sign out capability –
signOut()logs users out and clears all stored user data - Password reset feature –
forgotPassword()sends password reset emails to users who forgot their credentials - Error propagation – All methods properly handle and re-throw Firebase errors for your app to manage
- Main thread safety – Authentication methods use
@MainActorto ensure UI updates happen on the main thread - Complete auth flow – Covers registration, login, logout, and password recovery for a full authentication system
This manager provides everything needed for a complete email/password authentication system in your SwiftUI app.
Building a Firebase Registration Screen in SwiftUI
Creating a user registration form requires input validation, loading states, and error handling. Here’s a complete SwiftUI registration view that integrates with our Firebase AuthManager:
import SwiftUI
struct RegisterView: View {
@EnvironmentObject var authManager: AuthManager
@State private var email: String = ""
@State private var password: String = ""
@State private var confirmPassword: String = ""
@State private var isLoading = false
@State private var showError = false
@State private var errorMessage: String = ""
var body: some View {
VStack(spacing: 40) {
Text("Register to Firebase")
.font(.title)
.bold()
VStack(alignment: .leading, spacing: 20) {
VStack(alignment: .leading) {
Text("Enter email below:")
.font(.subheadline)
.foregroundColor(.secondary)
TextField("Enter email", text: $email)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
VStack(alignment: .leading) {
Text("Enter password below:")
.font(.subheadline)
.foregroundColor(.secondary)
SecureField("Enter password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
SecureField("Re-enter password", text: $confirmPassword)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
Button(action: {
Task {
do {
isLoading = true
try await authManager.createUser(email: email, password: password)
isLoading = false
}catch {
isLoading = false
errorMessage = error.localizedDescription
print("Registration failed: \(error.localizedDescription)")
showError = true
}
}
}) {
HStack {
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(0.8)
} else {
Text("Signup using Firebase")
}
}
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(8)
}
}
.alert("Registration failed!", isPresented: $showError) {
Button("OK") {
showError = false
}
} message: {
Text("\(errorMessage)")
}
.padding()
}
}
#Preview {
RegisterView()
}
What’s happening here:
- Environment object connection –
@EnvironmentObjectconnects the view to the shared AuthManager instance - State management –
@Stateproperties track user input, loading status, and error states - Form structure – VStack layout creates a clean registration form with proper spacing
- Input fields – TextField for email and SecureField for password entries with rounded border styling
- Password confirmation – Second SecureField allows users to verify their password input
- Async registration – Button action uses Task to handle the async
createUser()method - Loading indicator – ProgressView appears during registration process, replacing button text
- Error handling – Catches registration failures and displays user-friendly error messages
- Alert system – Shows error alerts with specific failure messages from Firebase
- Visual feedback – Button changes appearance during loading and shows appropriate content
- Clean UI design – Consistent styling with proper typography, colors, and spacing throughout
This registration view provides a complete user experience with proper validation, feedback, and error handling for Firebase authentication.
Creating a Complete Firebase Sign-In Screen with Navigation
This SwiftUI view provides a full authentication interface with sign-in functionality and navigation to registration and password reset screens. Here’s the complete implementation:
import SwiftUI
struct SignInView: View {
@EnvironmentObject var authManager: AuthManager
@State private var email: String = ""
@State private var password: String = ""
@State private var showError: Bool = false
@State private var errorMessage: String = ""
var body: some View {
NavigationStack {
VStack(spacing: 40) {
Text("Firebase AUTH tutorial")
.font(.title)
.bold()
VStack(alignment: .leading, spacing: 20) {
VStack(alignment: .leading, spacing: 3) {
Text("Enter your email below:")
.font(.subheadline)
.foregroundColor(.secondary)
TextField("Enter your email", text: $email)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
VStack(alignment: .leading, spacing: 3) {
Text("Enter your password below:")
.font(.subheadline)
.foregroundColor(.secondary)
SecureField("Enter your password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
VStackLayout(spacing: 10) {
Button(action: {
Task {
do {
try await authManager.signIn(email: email, password: password)
} catch {
print("Login failed: \(error.localizedDescription)")
errorMessage = error.localizedDescription
showError = true
}
}
}) {
Text("Login using Firebase")
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(8)
}
NavigationLink(destination:
RegisterView()
.environmentObject(authManager)
) {
Text("Register")
.font(.subheadline)
}
NavigationLink(destination:
ForgotPasswordView()
.environmentObject(authManager)
) {
Text("Forgot password")
.font(.subheadline)
}
}
}
.alert("Login failed!", isPresented: $showError) {
Button("OK") {
showError = false
}
} message: {
Text("\(errorMessage)")
}
.padding()
}
}
}
#Preview {
SignInView()
}
What’s happening here:
- Navigation wrapper –
NavigationStackenables navigation between sign-in, registration, and password reset screens - AuthManager integration –
@EnvironmentObjectconnects to the shared authentication manager - Input state management –
@Stateproperties track email, password, and error states - Clean form layout – VStack structure creates organized input sections with proper spacing
- Input fields – TextField for email and SecureField for password with rounded border styling
- Async sign-in – Button uses Task to handle the async
signIn()method from AuthManager - Error handling – Catches sign-in failures and displays specific error messages to users
- Navigation links – NavigationLink components provide seamless transitions to other authentication screens
- Environment object passing – Each destination view receives the AuthManager through
.environmentObject() - User-friendly navigation – Register and forgot password links offer alternative authentication paths
- Alert system – Error alerts show Firebase authentication failures with helpful messages
- Consistent styling – Uniform typography, colors, and spacing throughout the interface
This sign-in view serves as the main authentication hub, connecting users to all essential authentication functions in your Firebase-powered SwiftUI app.
Forgot password screen for Firebase authentication
When users forget their passwords, they need a simple way to reset them. Here’s a complete SwiftUI view that handles password reset requests through Firebase:
import SwiftUI
struct ForgotPasswordView: View {
@EnvironmentObject var authManager: AuthManager
@State private var email: String = ""
@State private var showSuccess = false
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
VStack(spacing: 40) {
Text("Forgot Password")
.font(.title)
.bold()
VStack(alignment: .leading, spacing: 20) {
VStack(alignment: .leading, spacing: 3) {
Text("Enter your email below:")
.font(.subheadline)
.foregroundColor(.secondary)
TextField("Enter your email", text: $email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.emailAddress)
.autocapitalization(.none)
}
}
VStackLayout(spacing: 10) {
Button(action: {
Task {
authManager.forgotPassword(email: email)
showSuccess = true
}
}) {
HStack {
Text("Send Reset Link")
}
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(8)
}
.disabled(email.isEmpty)
Button("Back to Sign In") {
dismiss()
}
.font(.subheadline)
}
}
.padding()
.alert("Reset Link Sent!", isPresented: $showSuccess) {
Button("OK") {
dismiss()
}
} message: {
Text("We've sent a password reset link to \(email). Please check your inbox.")
}
}
}
}
#Preview {
ForgotPasswordView()
}
What’s happening here:
- Environment integration –
@EnvironmentObjectconnects to the shared AuthManager and@Environment(\.dismiss)handles view dismissal - Simple state management –
@Stateproperties track email input and success alert visibility - Clean interface design – VStack layout creates a focused, single-purpose screen with proper spacing
- Email-optimized input – TextField configured with email keyboard type and disabled autocapitalization
- Smart button states – Send button is disabled when email field is empty to prevent invalid requests
- Password reset functionality – Calls
authManager.forgotPassword()to send reset email through Firebase - Success feedback – Alert confirms when reset link has been sent with personalized message
- Navigation handling – Back button and success alert both dismiss the view to return to sign-in
- User experience flow – Task wrapper handles the password reset request smoothly
- Accessibility features – Proper keyboard type and input formatting for better usability
- Clean visual hierarchy – Consistent styling matches other authentication screens in the app
This password reset view provides a streamlined experience for users who need to recover their accounts, integrating seamlessly with your Firebase authentication system.
Creating the Main Content View with Authentication Flow
The ContentView acts as your app’s traffic controller, deciding whether to show the authentication screens or the main app content based on user login status. Here’s how to implement this crucial navigation logic:
import SwiftUI
struct ContentView: View {
@StateObject private var authManager = AuthManager()
var body: some View {
if authManager.isSignedIn {
HomeView()
.environmentObject(authManager)
}else {
SignInView()
.environmentObject(authManager)
}
}
}
#Preview {
ContentView()
}
What’s happening here:
- Creates AuthManager –
@StateObjectmakes a single AuthManager for the whole app - Checks login status – Shows HomeView if signed in, SignInView if not
- Shares AuthManager –
.environmentObject()lets other views use the same AuthManager - Automatic updates – View changes when user signs in or out
This is your app’s main decision point – it decides what screen to show based on whether the user is logged in.
Basic Home Screen with Sign Out
This HomeView shows what authenticated users see, with a simple sign-out button:
import SwiftUI
struct HomeView: View {
@EnvironmentObject var authManager: AuthManager
var body: some View {
Text("Hello, World!")
Button {
Task {
try authManager.signOut()
}
}label: {
Text("Sign Out")
}
}
}
#Preview {
HomeView()
}
What’s happening here:
- Welcome text – Shows “Hello, World!” to logged-in users
- Sign out button – Logs the user out when tapped
- Auto navigation – App goes back to sign-in screen after sign out
Simple home screen that lets users sign out and return to the login flow.
Conclusion
You’ve created a professional-grade authentication system that would typically take weeks to build from scratch. Your SwiftUI app now includes secure registration, reliable sign-in, password recovery, and persistent sessions—all leveraging Firebase’s battle-tested security infrastructure.
Now you just need to build the rest of your application 😉
Get the Complete Code
Download the full source code on GitHub →
Frequently Asked Questions – SwiftUI Firebase Authentication
Auth domain not authorized” error – what does this mean?
In Firebase Console, go to Authentication > Settings > Authorized domains and add your development/production domains.
Can I customize the password reset email template?
Yes, in Firebase Console go to Authentication > Templates to customize email templates, including branding and content.
Do I need a paid Firebase account to use authentication?
No, Firebase Authentication is free for up to 10,000 monthly active users. The free tier includes email/password, phone, and social logins.
Why am I getting “GoogleService-Info.plist not found” error?
Ensure the .plist file is added to your Xcode project (not just the folder) and is included in your app target. Check that the bundle identifier matches your Firebase project.
How do I handle authentication state across app launches?
Firebase automatically maintains authentication state. The checkAuthStatus() method in AuthManager detects existing sessions when the app starts.
How do I implement proper password validation?
Add validation in your registration view: minimum 8 characters, mix of letters/numbers/symbols. Firebase also provides built-in password strength requirements.
Can I add social login (Google, Apple) to this setup?
Yes, you can extend the AuthManager to include social providers. You’ll need to add the respective Firebase provider dependencies and configure them in your Firebase console.