Flutter 3 Firebase Authentication Tutorial: Email, Google & Apple Sign-In

by Didin J. on Feb 03, 2026 Flutter 3 Firebase Authentication Tutorial: Email, Google & Apple Sign-In

Learn how to implement Firebase Authentication in Flutter 3 using Email/Password, Google Sign-In, and Apple Sign-In for Android and iOS.

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

  1. Open the Firebase Console

  2. Click Add project

  3. Enter a project name, for example: Flutter Firebase Auth

  1. Click Continue

  2. Google Analytics:

    • Optional (can be enabled or disabled)

    • If unsure, enable it (it won’t affect authentication)

  3. Click Create project

  4. 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

  1. In Firebase Console → Project Overview

  2. Click Add appAndroid

  3. Enter:

    • Android package name:

       
      com.example.flutter_firebase_auth

       

  4. Nickname (optional)

  5. Do not upload SHA-1 yet (we’ll add it later for Google Sign-In)

  6. 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 root android/ 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

  1. Firebase Console → Add appiOS

  2. Enter:

    • iOS bundle ID (must match Xcode exactly)

  3. App nickname (optional)

  4. Skip App Store ID

  5. 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.

  1. Firebase Console → BuildAuthentication

  2. Click Get started

  3. 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_core added

  • ✔ 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:

  1. Register with a new email

  2. Logout

  3. Log in with the same credentials

  4. Restart the app (session persists)

  5. 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

  • AuthGate switches to LoginScreen

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:

  1. Firebase Console → BuildAuthentication

  2. Go to Sign-in method

  3. Enable Google

  4. Select a Project support email

  5. 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

  1. Firebase Console → Project settings

  2. Select your Android app

  3. Click Add fingerprint

  4. Paste the SHA-1

  5. 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:

  1. Firebase Console → Authentication

  2. Go to Sign-in method

  3. Enable Apple

  4. 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:

  1. Select Runner

  2. Go to Signing & Capabilities

  3. Click + Capability

  4. 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:

  1. Ask the user to log in with the existing method

  2. 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:

  1. Flutter app sends Firebase ID token

  2. Backend verifies token using Firebase Admin SDK

  3. 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:

Thanks!