Authentication is a fundamental requirement in almost every modern mobile application. Whether you’re building a personal project, a startup MVP, or a production-grade app, users expect a secure and seamless way to sign in using familiar methods such as email or social accounts.
With Flutter 3 and Firebase Authentication, implementing a robust authentication system becomes significantly easier. Firebase Auth handles user management, credential storage, secure token generation, and session persistence—allowing you to focus on building your app’s features instead of backend infrastructure.
In this tutorial, we’ll build a complete authentication flow in Flutter using Firebase Authentication, covering the three most widely used sign-in methods:
-
Email & Password Authentication – the most flexible and universally supported option
-
Google Sign-In – a fast, low-friction login experience, especially popular on Android
-
Apple Sign-In – a privacy-focused and mandatory option for iOS apps that offer social login
You’ll learn not only how to enable each provider in Firebase, but also how to integrate them properly in Flutter, handle authentication state, and build a clean, production-ready login flow that works on both Android and iOS.
What makes this tutorial different?
This guide is written with real-world usage in mind:
-
Uses Flutter 3 and Dart 3 (no deprecated APIs)
-
Covers platform-specific configuration (Android & iOS)
-
Explains why certain steps are required, not just what to do
-
Focuses on best practices and common pitfalls
-
Provides a reusable authentication foundation for future projects
By the end of this tutorial, you’ll have a fully working Flutter authentication system that you can confidently adapt for client projects or production apps.
Prerequisites
Before we start building the Flutter authentication app, make sure your development environment is properly set up. This tutorial assumes basic familiarity with Flutter and mobile development concepts.
Required Knowledge
You should be comfortable with:
-
Basic Flutter widgets and app structure
-
Writing and running Flutter apps on Android or iOS
-
Basic understanding of authentication concepts (login, logout, sessions)
No prior experience with Firebase is required.
Software & Tools
Ensure the following tools are installed and properly configured:
Flutter SDK
-
Flutter SDK 3.x
-
Dart 3 included
-
Verify installation:
flutter doctor
IDE
Any of the following:
-
Android Studio
-
Visual Studio Code with Flutter & Dart extensions
-
Xcode (required for iOS builds)
Firebase Account
-
A Google account
-
Access to the Firebase Console
-
Ability to create new Firebase projects
Platform Requirements
Android
-
Android SDK installed
-
Android emulator or physical device
-
Google Play services enabled
-
SHA-1 key access (needed for Google Sign-In)
iOS
-
macOS with Xcode installed
-
iOS simulator or physical iPhone
-
Apple Developer Account (required for Apple Sign-In)
-
Apple Sign-In capability enabled in Xcode
⚠️ Important: Apple Sign-In is mandatory for iOS apps that provide third-party login options like Google or Facebook.
Supported Platforms in This Tutorial
This guide focuses on:
-
✅ Android
-
✅ iOS
Web and desktop authentication are not covered to keep the tutorial focused and production-oriented.
Final Checklist Before You Start
Make sure you have:
-
✔ Flutter 3.x installed and working
-
✔ Android and/or iOS development environment ready
-
✔ Firebase account access
-
✔ Apple Developer account (for Apple Sign-In)
Once everything is ready, we’ll move on to creating the Flutter project and setting up Firebase in the next section.
Create a New Flutter 3 Project
In this section, we’ll create a brand-new Flutter 3 project that will serve as the foundation for our Firebase Authentication implementation. We’ll start with the standard Flutter template and then prepare the project structure so it’s easy to maintain and extend as we add authentication features.
1. Verify Flutter Installation
Before creating the project, make sure Flutter is installed correctly and all required dependencies are available.
Run the following command:
flutter doctor
You should see a green checkmark for:
-
Flutter SDK
-
Dart SDK
-
Android toolchain (for Android)
-
Xcode (for iOS, macOS only)
If any issues are reported, resolve them before continuing. A clean flutter doctor output will save you a lot of time later, especially when integrating Firebase.
2. Create a New Flutter Project
Open a terminal and run:
flutter create flutter_firebase_auth
cd flutter_firebase_auth
This command generates a fully working Flutter 3 application using the default Material template.
To confirm everything works, run the app:
flutter run
You should see the default Flutter counter app running on your emulator or physical device.
3. Understand the Default Project Structure
After creating the project, you’ll see a structure similar to this:
flutter_firebase_auth/
├── android/
├── ios/
├── lib/
│ └── main.dart
├── pubspec.yaml
└── test/
Key folders and files:
-
android/ – Android-specific configuration and build files
-
ios/ – iOS-specific configuration and Xcode project
-
lib/ – Main Flutter application code
-
main.dart – Application entry point
-
pubspec.yaml – Project dependencies and assets
At this stage, we’ll keep the default setup and refactor the structure in a controlled way to support authentication features.
4. Prepare a Clean Project Structure
As the app grows, keeping authentication logic organized is critical. Let’s prepare a simple and scalable folder structure.
Inside the lib/ directory, create the following folders:
lib/
├── auth/
├── screens/
├── services/
├── widgets/
└── main.dart
Folder responsibilities:
-
auth/ – Authentication-related logic (providers, helpers)
-
screens/ – UI screens (login, register, home)
-
services/ – Firebase and external service integrations
-
widgets/ – Reusable UI components (buttons, inputs)
This structure keeps UI, logic, and services clearly separated—ideal for real-world Flutter apps.
5. Clean Up main.dart
Open lib/main.dart and replace its contents with a minimal app shell:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Firebase Auth',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.blue,
),
home: const Scaffold(
body: Center(
child: Text(
'Flutter Firebase Authentication',
style: TextStyle(fontSize: 18),
),
),
),
);
}
}
This stripped-down version:
-
Removes demo counter logic
-
Uses Material 3
-
Gives us a clean starting point before adding Firebase initialization
6. Why Start Simple?
At this stage, we intentionally do not add Firebase or authentication logic yet. Keeping the project minimal helps:
-
Isolate setup issues early
-
Avoid Firebase initialization errors
-
Ensure Android & iOS builds are stable before adding plugins
Once this foundation is verified, we’ll integrate Firebase step by step in the next section.
7. Commit the Baseline (Optional but Recommended)
If you’re using Git, this is a great checkpoint:
git init
git add .
git commit -m "Initial Flutter 3 project setup"
This allows you to easily roll back if something goes wrong during Firebase configuration.
Create and Configure a Firebase Project
In this section, we’ll create a new Firebase project and connect it to our Flutter application for both Android and iOS. Firebase will handle authentication, user sessions, and secure token management across platforms.
We’ll configure Firebase manually via the Firebase Console to keep everything explicit and beginner-friendly.
1. Create a New Firebase Project
-
Open the Firebase Console
-
Click Add project
-
Enter a project name, for example:
Flutter Firebase Auth
-
Click Continue
-
Google Analytics:
-
Optional (can be enabled or disabled)
-
If unsure, enable it (it won’t affect authentication)
-
-
Click Create project
-
Wait until Firebase finishes provisioning the project
Once complete, you’ll be redirected to the Firebase project dashboard.
2. Add Firebase to the Android App
Now we’ll register the Android version of our Flutter app with Firebase.
Step 1: Get the Android Package Name
Open this file in your Flutter project:
android/app/build.gradle.kts
Look for:
applicationId "com.example.flutter_firebase_auth"
Copy the value — this is your Android package name.
Step 2: Register the Android App in Firebase
-
In Firebase Console → Project Overview
-
Click Add app → Android
-
Enter:
-
Android package name:
com.example.flutter_firebase_auth
-
-
Nickname (optional)
-
Do not upload SHA-1 yet (we’ll add it later for Google Sign-In)
-
Click Register app
Step 3: Download google-services.json
-
Download the configuration file
-
Place it in:
android/app/google-services.json
⚠️ Make sure the file is inside
android/app/, not the rootandroid/folder.
Step 4: Enable Google Services (Gradle)
Open:
android/settings.gradle.kts
Add inside plugins:
id("com.google.gms.google-services") version "4.4.1" apply false
Then open:
android/app/build.gradle.kts
At the top:
apply plugin: 'com.google.gms.google-services'
3. Add Firebase to the iOS App
Next, let’s register the iOS app.
Step 1: Get the iOS Bundle Identifier
Open:
ios/Runner.xcodeproj
Or from terminal:
open ios/Runner.xcworkspace
In Xcode:
-
Select Runner
-
Go to Signing & Capabilities
-
Copy the Bundle Identifier, for example:
com.example.flutterFirebaseAuth
Step 2: Register the iOS App in Firebase
-
Firebase Console → Add app → iOS
-
Enter:
-
iOS bundle ID (must match Xcode exactly)
-
-
App nickname (optional)
-
Skip App Store ID
-
Click Register app
Step 3: Download GoogleService-Info.plist
-
Download the file
-
Move it into:
ios/Runner/ -
In Xcode, ensure:
-
Copy items if needed is checked
-
Target Runner is selected
-
4. Enable Firebase Authentication Providers
With both platforms registered, we can now enable authentication.
-
Firebase Console → Build → Authentication
-
Click Get started
-
Go to Sign-in method
Enable the following providers:
Email/Password
-
Enable Email/Password
-
(Optional) Enable Email link (passwordless) if needed
-
Click Save
Google Sign-In
-
Enable Google
-
Set a Project support email
-
Click Save
⚠️ Google Sign-In will require SHA-1 configuration later (covered in the Google Sign-In section).
Apple Sign-In
-
Enable Apple
-
Click Save
ℹ️ Apple Sign-In will only work on iOS and requires additional Apple Developer configuration later.
5. Verify Firebase Setup
At this point, your Firebase project should have:
-
✅ Android app registered
-
✅ iOS app registered
-
✅ Firebase Authentication enabled
-
✅ Email, Google, and Apple providers are active
You should not see any errors or warnings in the Firebase Console.
6. Why We Don’t Add Firebase SDK Yet
We intentionally stop here before adding Firebase packages to Flutter because:
-
Platform setup errors are easier to fix early
-
Firebase plugins are sensitive to misconfiguration
-
This keeps troubleshooting isolated and predictable
In the next section, we’ll use official Flutter Firebase plugins and initialize Firebase correctly in Flutter.
Add Firebase Dependencies & Initialize Firebase in Flutter
With Firebase configured for Android and iOS, we can now integrate Firebase directly into our Flutter application. In this section, we’ll add the required Firebase packages, initialize Firebase safely, and verify that everything is connected correctly.
1. Add Firebase Core Dependency
Firebase features in Flutter are modular, but all Firebase services require firebase_core.
Open pubspec.yaml. Add or update the dependencies section:
dependencies:
flutter:
sdk: flutter
firebase_core: ^4.4.0
Then run:
flutter pub get
2. Initialize Firebase in main.dart
Now we’ll initialize Firebase before running the app.
Open lib/main.dart. Update it as follows:
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Firebase Auth',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.blue,
),
home: const Scaffold(
body: Center(
child: Text(
'Firebase Initialized Successfully',
style: TextStyle(fontSize: 18),
),
),
),
);
}
}
Why WidgetsFlutterBinding.ensureInitialized()?
This ensures Flutter is fully ready before Firebase initializes—required when calling async code in main().
3. Platform-Specific Firebase Initialization (What’s Happening Behind the Scenes)
Because we:
-
Added
google-services.json(Android) -
Added
GoogleService-Info.plist(iOS) -
Enabled Google Services plugin correctly (Kotlin DSL)
Firebase automatically picks up the correct configuration per platform.
No platform-specific Dart code is required at this stage.
4. Verify Firebase Initialization
Run the app:
flutter run
If everything is correct:
-
✅ App launches normally
-
✅ No red error screens
-
✅ Console shows no Firebase initialization errors
At this stage:
-
Firebase Auth is enabled
-
Firebase SDK is connected
-
Auth providers are configured in the Firebase Console
-
We haven’t implemented login logic yet — that’s intentional
5. Common Firebase Initialization Errors (And Fixes)
❌ FirebaseException: [core/no-app]
Cause: Firebase initialized before Flutter binding
Fix: Ensure WidgetsFlutterBinding.ensureInitialized() exists
❌ App crashes immediately on Android
Cause: google-services.json missing or misplaced
Fix: Verify location:
android/app/google-services.json
❌ iOS build fails
Cause: GoogleService-Info.plist not added to Runner target
Fix: Open Xcode → Runner → Build Phases → Copy Bundle Resources
6. Why We Don’t Add Auth Packages Yet
We intentionally stop at Firebase Core here because:
-
Firebase initialization must work before adding auth logic
-
Makes debugging much easier
-
Prevents cascading plugin errors
Once this step is stable, adding authentication providers becomes trivial.
✅ Section Checklist
Before moving on, confirm:
-
✔
firebase_coreadded -
✔ Firebase initializes without errors
-
✔ App runs on Android and/or iOS
-
✔ No red screens or Gradle/Xcode failures
Email & Password Authentication (Register, Login, Logout)
In this section, we’ll implement Email & Password authentication using Firebase Authentication in a Flutter 3 app. This method is widely supported, easy to customize, and works consistently across Android and iOS.
We’ll cover:
-
User registration
-
User login
-
Logout
-
Basic error handling
1. Add Firebase Auth Dependency
Open pubspec.yaml and add:
dependencies:
firebase_auth: ^6.1.4
Then run:
flutter pub get
2. Create an Authentication Service
To keep logic clean and reusable, we’ll isolate Firebase Auth logic into a service class.
Create a new file:
lib/services/auth_service.dart
Add the following code:
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Register with email & password
Future<User?> register({
required String email,
required String password,
}) async {
final credential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return credential.user;
}
// Login with email & password
Future<User?> login({
required String email,
required String password,
}) async {
final credential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return credential.user;
}
// Logout
Future<void> logout() async {
await _auth.signOut();
}
User? get currentUser => _auth.currentUser;
}
Why this approach?
-
Keeps Firebase logic out of UI
-
Easy to extend later (Google, Apple)
-
Cleaner testing and maintenance
3. Create the Login Screen
Create a new file:
lib/screens/login_screen.dart
import 'package:flutter/material.dart';
import '../services/auth_service.dart';
import 'home_screen.dart';
import 'register_screen.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _authService = AuthService();
bool _loading = false;
Future<void> _login() async {
setState(() => _loading = true);
try {
await _authService.login(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
} finally {
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _loading ? null : _login,
child: _loading
? const CircularProgressIndicator()
: const Text('Login'),
),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const RegisterScreen(),
),
);
},
child: const Text('Create an account'),
),
],
),
),
);
}
}
4. Create the Register Screen
Create:
lib/screens/register_screen.dart
import 'package:flutter/material.dart';
import '../services/auth_service.dart';
import 'home_screen.dart';
class RegisterScreen extends StatefulWidget {
const RegisterScreen({super.key});
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _authService = AuthService();
bool _loading = false;
Future<void> _register() async {
setState(() => _loading = true);
try {
await _authService.register(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
} finally {
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Register')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _loading ? null : _register,
child: _loading
? const CircularProgressIndicator()
: const Text('Register'),
),
],
),
),
);
}
}
5. Create the Home Screen (Logout)
Create:
lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import '../services/auth_service.dart';
import 'login_screen.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final authService = AuthService();
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await authService.logout();
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const LoginScreen()),
);
},
),
],
),
body: const Center(
child: Text(
'You are logged in 🎉',
style: TextStyle(fontSize: 18),
),
),
);
}
}
6. Update main.dart Entry Screen
Temporarily start the app with the login screen:
home: const LoginScreen(),
(We’ll improve this later using an auth state listener.)
7. Testing Email & Password Authentication
Test the following flow:
-
Register with a new email
-
Logout
-
Log in with the same credentials
-
Restart the app (session persists)
-
Logout again
Check Firebase Console → Authentication → Users
You should see newly created users listed.
8. Common Errors & Fixes
| Error | Cause |
|---|---|
email-already-in-use |
Account already exists |
weak-password |
Password < 6 chars |
invalid-email |
Incorrect email format |
user-not-found |
Email not registered |
✅ Section Checklist
-
✔ Email registration works
-
✔ Login works
-
✔ Logout works
-
✔ Users appear in Firebase Console
-
✔ No crashes or red screens
Authentication State Listener (Auto Login & Session Handling)
Manually navigating users after login and logout works, but it doesn’t scale well and often causes bugs—especially when the app restarts. A better approach is to listen to Firebase authentication state changes and react automatically.
In this section, we’ll implement an authentication state listener so that:
-
Logged-in users are automatically redirected to the home screen
-
Logged-out users are sent to the login screen
-
Sessions persist across app restarts
-
No manual navigation logic is needed after login/logout
This is the recommended production pattern when using Firebase Authentication with Flutter 3.
1. How Firebase Auth State Works
Firebase automatically stores the user’s authentication session on the device.
Whenever the authentication state changes—login, logout, app restart—Firebase emits an event.
We can listen to this stream using:
FirebaseAuth.instance.authStateChanges()
This stream returns:
-
User→ when the user is logged in -
null→ when the user is logged out
2. Create an Auth Gate (Wrapper Widget)
Instead of choosing a screen manually in main.dart, we’ll create an Auth Gate widget that decides which screen to show.
Create a new file:
lib/auth/auth_gate.dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../screens/home_screen.dart';
import '../screens/login_screen.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
// Still checking auth state
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
// User logged in
if (snapshot.hasData) {
return const HomeScreen();
}
// User not logged in
return const LoginScreen();
},
);
}
}
Why this works so well
-
Reacts instantly to login/logout
-
Handles app restarts automatically
-
Eliminates navigation bugs
-
Keeps auth logic centralized
3. Update main.dart to Use Auth Gate
Open:
lib/main.dart
Replace the home: widget with AuthGate:
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'auth/auth_gate.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Firebase Auth',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.blue,
),
home: const AuthGate(),
);
}
}
That’s it — no more manual routing after login or logout.
4. Simplify Login & Logout Logic
Now that the auth listener controls navigation, we can remove manual screen redirects.
Login / Register
You no longer need:
Navigator.pushReplacement(...)
Just call:
await _authService.login(...);
Firebase will emit a new auth state → AuthGate updates automatically.
Logout
Your existing logout code still works:
await authService.logout();
Once signOut() is called:
-
Firebase emits
null -
AuthGateswitches toLoginScreen
No navigation code required.
5. Session Persistence (What Happens on App Restart?)
Firebase automatically:
-
Stores the auth token securely
-
Restores the session on app restart
-
Emits the authenticated user again
Result:
-
User opens the app
-
Sees a loading spinner briefly
-
Lands directly on HomeScreen
This is exactly how production apps behave.
6. Optional: Display Current User Info
You can easily access the logged-in user anywhere:
final user = FirebaseAuth.instance.currentUser;
print(user?.email);
This is useful for:
-
Showing user email on profile screen
-
Fetching user-specific data
-
Role-based logic (later with custom claims)
7. Common Issues & Fixes
| Problem | Cause |
|---|---|
| App stuck loading | Firebase not initialized |
| Always shows login | User not authenticated |
| UI flickers | Avoid manual navigation |
| AuthGate not updating | Wrong stream used |
Always use:
authStateChanges()
—not userChanges() or idTokenChanges() for basic routing.
✅ Section Checklist
-
✔ AuthGate implemented
-
✔ Auto login works
-
✔ Logout updates UI automatically
-
✔ App restart restores session
-
✔ No manual navigation needed
Google Sign-In Authentication (Android & iOS)
Google Sign-In provides a fast, low-friction login experience and is one of the most widely used authentication methods in mobile apps. In this section, we’ll integrate Google Sign-In with Firebase Authentication in a Flutter 3 app.
We’ll cover:
-
Firebase Console configuration
-
Android SHA-1 setup
-
Flutter Google Sign-In integration
-
Linking Google accounts to Firebase users
-
UI integration with our existing AuthGate
1. Enable Google Sign-In in Firebase
If you haven’t already:
-
Firebase Console → Build → Authentication
-
Go to Sign-in method
-
Enable Google
-
Select a Project support email
-
Click Save
2. Add SHA-1 Fingerprint (Android Only)
Google Sign-In requires a SHA-1 key for Android.
Get SHA-1 from Flutter
Run:
cd android
./gradlew signingReport
Look for:
SHA1: XX:XX:XX:XX:...
Copy the SHA-1 value.
Add SHA-1 to Firebase
-
Firebase Console → Project settings
-
Select your Android app
-
Click Add fingerprint
-
Paste the SHA-1
-
Save changes
⚠️ Without SHA-1, Google Sign-In will fail silently on Android.
3. Add Google Sign-In Dependency
Open pubspec.yaml:
dependencies:
google_sign_in: ^7.2.0
Then run:
flutter pub get
4. Update Auth Service for Google Sign-In
Open:
lib/services/auth_service.dart
Then add this method inside AuthService:
Future<User?> signInWithGoogle() async {
final googleProvider = GoogleAuthProvider();
final userCredential =
await _auth.signInWithProvider(googleProvider);
return userCredential.user;
}
5. Add Google Sign-In Button to Login Screen
Open:
lib/screens/login_screen.dart
Add this button below the email/password login button:
const SizedBox(height: 12),
OutlinedButton.icon(
icon: const Icon(Icons.login),
label: const Text('Sign in with Google'),
onPressed: _loading
? null
: () async {
try {
await _authService.signInWithGoogle();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
}
},
),
Thanks to AuthGate, no navigation code is needed.
6. Test Google Sign-In
Android
-
Test on a real device or an emulator with Google Play Services
-
Choose a Google account
-
App should redirect to HomeScreen
iOS
-
Google Sign-In works automatically using the Firebase configuration
-
No extra setup required (unlike Apple Sign-In)
7. Account Linking Behavior
Firebase automatically:
-
Creates a new user if the Google email doesn’t exist
-
Links Google login to an existing email/password account (if emails match)
This prevents duplicate user accounts — a huge win.
8. Common Errors & Fixes
| Error | Cause |
|---|---|
| Sign-in pop-up closes | SHA-1 missing |
ApiException: 10 |
Wrong SHA-1 |
| No account picker | Google Play Services is missing |
| Works on iOS, not Android | SHA-1 not registered |
✅ Section Checklist
-
✔ Google provider enabled
-
✔ SHA-1 added (Android)
-
✔ Google Sign-In works
-
✔ AuthGate handles navigation
-
✔ Users appear in Firebase Console
Apple Sign-In Authentication (iOS)
Apple Sign-In is a privacy-focused authentication method and is required by Apple if your iOS app offers any third-party login options, such as Google or Facebook.
In this section, we’ll integrate Apple Sign-In with Firebase Authentication in a Flutter 3 app, following Apple-compliant and production-ready practices.
1. Requirements & Limitations
Before starting, make sure you have:
-
macOS with Xcode
-
iOS device or simulator
-
Apple Developer Account (required)
-
Apple Sign-In enabled in Firebase
-
iOS target only (Apple Sign-In does not work on Android)
2. Enable Apple Sign-In in Firebase
If not already done:
-
Firebase Console → Authentication
-
Go to Sign-in method
-
Enable Apple
-
Click Save
No keys or secrets are needed in Firebase for basic Apple Sign-In.
3. Enable “Sign In with Apple” in Xcode
Open your iOS project:
open ios/Runner.xcworkspace
In Xcode:
-
Select Runner
-
Go to Signing & Capabilities
-
Click + Capability
-
Add Sign In with Apple
✔ Make sure your Apple Developer Team is selected
✔ Xcode will auto-configure entitlements
4. Add Apple Sign-In Dependency
Open pubspec.yaml and add:
dependencies:
sign_in_with_apple: ^8.0.0-spm.1
Then run:
flutter pub get
5. Implement Apple Sign-In in Auth Service
We’ll add Apple Sign-In support directly through Firebase, similar to the Google provider approach.
Open:
lib/services/auth_service.dart
Add imports:
import 'dart:math';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
Helper Methods (Nonce Handling – Important)
Add these inside AuthService:
String _generateNonce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(
length,
(_) => charset[random.nextInt(charset.length)],
).join();
}
String _sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
Apple Sign-In Method
Future<User?> signInWithApple() async {
final rawNonce = _generateNonce();
final nonce = _sha256ofString(rawNonce);
final appleCredential =
await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
final userCredential =
await _auth.signInWithCredential(oauthCredential);
return userCredential.user;
}
Why nonce matters
-
Required by Firebase
-
Prevents replay attacks
-
Mandatory for Apple Sign-In compliance
6. Add Apple Sign-In Button (iOS Only)
Update:
lib/screens/login_screen.dart
Add import:
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
Add this below the Google button:
if (Theme.of(context).platform == TargetPlatform.iOS)
Padding(
padding: const EdgeInsets.only(top: 12),
child: SignInWithAppleButton(
onPressed: () async {
try {
await _authService.signInWithApple();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
}
},
),
),
Apple requires official button styling, which this widget provides automatically.
7. Test Apple Sign-In
Simulator
-
Works on iOS 13+
-
Use the Apple ID test account
Real Device
-
Must be signed into iCloud
-
Face ID / Touch ID confirmation required
After login:
-
AuthGate redirects to HomeScreen
-
User appears in Firebase Console
-
Provider shows as apple.com
8. Common Apple Sign-In Errors
| Error | Cause |
|---|---|
| Capability missing | Not added in Xcode |
invalid-credential |
Nonce mismatch |
| Button not visible | Not on iOS |
| Crash on login | Missing entitlement |
✅ Section Checklist
-
✔ Apple provider enabled in Firebase
-
✔ Sign In with Apple capability added
-
✔ Nonce implemented correctly
-
✔ Apple login works on iOS
-
✔ AuthGate handles navigation
🎉 Authentication Complete!
At this point, your Flutter app supports:
-
✅ Email & Password
-
✅ Google Sign-In
-
✅ Apple Sign-In
-
✅ Auto login & session persistence
-
✅ Production-ready auth architecture
Security Best Practices & Account Linking
With Email/Password, Google Sign-In, and Apple Sign-In implemented, your Flutter app now has a powerful authentication system. However, security and account consistency are just as important as functionality.
In this section, we’ll cover essential Firebase Authentication security best practices and explain how account linking works to prevent duplicate users when multiple sign-in methods are used.
1. Use Firebase as the Single Source of Truth
Always treat Firebase Authentication as the authoritative source for user identity.
Best practices:
-
Never store passwords yourself
-
Never trust client-side user IDs without Firebase verification
-
Always rely on
FirebaseAuth.instance.currentUser
Firebase securely handles:
-
Password hashing
-
Token issuance and refresh
-
Session persistence
2. Enforce Email Verification (Recommended)
For Email/Password accounts, email verification prevents fake or mistyped accounts.
Send Verification Email
await FirebaseAuth.instance.currentUser
?.sendEmailVerification();
Check Verification Status
final user = FirebaseAuth.instance.currentUser;
if (user != null && !user.emailVerified) {
// Restrict access or show warning
}
Best practice:
-
Allow login
-
Restrict sensitive actions until verified
3. Secure Logout & Session Handling
Firebase automatically:
-
Refreshes tokens securely
-
Invalidates tokens on logout
-
Restores sessions safely
Your responsibility:
-
Call
signOut()when logging out -
Do not cache auth tokens manually
-
Use AuthGate for routing
await FirebaseAuth.instance.signOut();
4. Prevent Duplicate Accounts with Account Linking
Firebase supports account linking, allowing one user to sign in using multiple providers.
Example:
-
User signs up with Email/Password
-
Later signs in with Google using the same email
-
Firebase links both methods to one user
Check Linked Providers
final user = FirebaseAuth.instance.currentUser;
for (final info in user?.providerData ?? []) {
print(info.providerId);
}
Possible values:
-
password -
google.com -
apple.com
5. Manual Account Linking (Advanced)
In rare cases, you may want to explicitly link providers.
Link Google to Existing Account
final googleProvider = GoogleAuthProvider();
await FirebaseAuth.instance.currentUser
?.linkWithProvider(googleProvider);
Link Apple to Existing Account
final appleProvider = OAuthProvider("apple.com");
await FirebaseAuth.instance.currentUser
?.linkWithProvider(appleProvider);
⚠️ Always link after the user is authenticated.
6. Handle Account-Exists-With-Different-Credential
This error happens when:
-
User tries Google login
-
Email already exists with Email/Password
Firebase throws:
account-exists-with-different-credential
Recommended flow:
-
Ask the user to log in with the existing method
-
Link the new provider after login
This ensures:
-
No duplicate accounts
-
Clean user history
7. Disable Unused Providers
In Firebase Console:
-
Disable providers you don’t use
-
Reduces attack surface
-
Simplifies login UX
Only enable:
-
Email/Password
-
Google
-
Apple (if iOS)
8. Protect Backend APIs (Important)
If your app talks to a backend API:
-
Always verify Firebase ID tokens server-side
-
Never trust client claims blindly
Recommended flow:
-
Flutter app sends Firebase ID token
-
Backend verifies token using Firebase Admin SDK
-
Backend authorizes the request
This applies to:
-
REST APIs
-
GraphQL APIs
-
Serverless functions
9. Rate Limiting & Abuse Protection
Firebase provides built-in protection against:
-
Brute-force login attempts
-
Excessive password resets
You should:
-
Avoid exposing auth endpoints unnecessarily
-
Add CAPTCHA for sensitive flows (optional)
10. Security Checklist
Before going to production, confirm:
-
✔ Email verification implemented
-
✔ AuthGate controls navigation
-
✔ Providers properly linked
-
✔ Unused providers disabled
-
✔ Backend validates Firebase tokens
-
✔ No passwords stored locally
🎯 Key Takeaways
-
Firebase Auth handles most security concerns — use it correctly
-
Account linking prevents duplicate users
-
Email verification adds trust
-
AuthGate simplifies secure routing
-
Always validate auth tokens on the backend
Final Conclusion
Authentication is one of the most critical foundations of any modern mobile application, and getting it right early saves countless hours of refactoring and security fixes later. In this tutorial, we built a complete, production-ready authentication system using Flutter 3 and Firebase Authentication, covering the most widely used login methods on mobile.
Starting from a clean Flutter project, we configured Firebase correctly for Android and iOS, integrated Email/Password authentication, added Google Sign-In and Apple Sign-In, and implemented an authentication state listener to handle auto login and session persistence. We then reinforced the system with security best practices, email verification, and account linking to ensure a scalable and maintainable authentication flow.
This architecture mirrors how real-world production apps handle authentication today:
-
Firebase acts as the single source of truth
-
Flutter focuses on UI and state
-
Authentication state drives navigation
-
Providers are linked cleanly to avoid duplicate users
You can now confidently reuse this setup as a starting point for:
-
Startup MVPs
-
Client projects
-
Internal tools
-
Full-scale production apps
From here, the natural next steps are integrating Firestore or REST APIs, implementing role-based access control, and enhancing the UI with profile and settings screens—all built on top of the solid authentication foundation you’ve created.
You can find the full source code on our GitHub.
We know that building beautifully designed Mobile and Web Apps from scratch can be frustrating and very time-consuming. Check Envato unlimited downloads and save development and design time.
That's just the basics. If you need more deep learning about Flutter and Dart, you can take the following cheap course:
- Flutter & Dart - The Complete Guide [2025 Edition]
- The Complete Flutter Development Bootcamp with Dart
- Complete Flutter Guide 2025: Build Android, iOS and Web apps
- Advanced Flutter: Build Enterprise-Ready Apps.
- Flutter , Nodejs, Express , MongoDB: Build Multi-Store App
- Flutter & Dart Essentials-Build Mobile Apps like a Pro
- Master Flutter with ML: Object Detection, OCR & Beyond
- Full-Stack Mobile Development: Flutter, Figma, and Firebase
- Dart & Flutter - Zero to Mastery [2025] + Clean Architecture
- Master Flame Game Engine with Flutter: Build 2D Mobile Games
Thanks!
