image

15 Feb 2026

9K

35K

Flutter & Firebase Auth: Implementing Automatic Email Verification

Email verification is a crucial step in securing user accounts and ensuring the authenticity of user data. When building applications with Flutter, Firebase Authentication provides a robust and straightforward solution for managing user authentication, including email verification. This article will guide you through the process of implementing automatic email verification using Flutter and Firebase Auth, enhancing your application's security and user experience.

Why Email Verification?

Verifying a user's email address offers several benefits:

  • Enhanced Security: Prevents unauthorized access and confirms user identity.
  • Spam Prevention: Reduces the likelihood of fake accounts and spam sign-ups.
  • Account Recovery: Ensures that users can recover their accounts via a verified email.
  • Improved Communication: Guarantees that important notifications reach the correct inbox.

Prerequisites

Before diving into the implementation, ensure you have the following:

  • A Google account.
  • Flutter SDK installed and configured.
  • A basic understanding of Flutter development.
  • A basic understanding of Firebase.

Step 1: Firebase Project Setup

First, set up your Firebase project:

  1. Go to the Firebase Console.
  2. Click "Add project" and follow the steps to create a new project.
  3. Once your project is created, navigate to "Authentication" in the left sidebar.
  4. Click "Get started", then select the "Email/Password" provider and enable it.
  5. Add your Flutter app to the Firebase project (Android, iOS, Web, etc.) by following the instructions in the Firebase Console. This typically involves downloading configuration files (google-services.json for Android, GoogleService-Info.plist for iOS) and adding them to your Flutter project, along with some configuration in your platform-specific build files.

Step 2: Flutter Project Setup and Dependencies

Create a new Flutter project or open an existing one. Add the necessary Firebase dependencies to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^latest_version
  firebase_auth: ^latest_version

After adding the dependencies, run flutter pub get to fetch them.

Initialize Firebase in your main.dart file. It's recommended to do this before running your app:


import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(); // Initialize Firebase
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firebase Email Auth',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AuthWrapper(), // We'll create this widget later
    );
  }
}

Step 3: User Registration and Sending Verification Email

When a user registers, you'll create their account using Firebase Authentication. Immediately after successful registration, you can automatically send a verification email. It's crucial to ensure the user is logged in after registration to be able to send the verification email to their current session.

Here's an example of a registration function:


Future<void> registerUserAndSendVerification(String email, String password) async {
  try {
    UserCredential userCredential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
      email: email,
      password: password,
    );

    User? user = userCredential.user;
    if (user != null) {
      await user.sendEmailVerification();
      print("Verification email sent to ${user.email}");
      // Optionally, sign out the user to force them to log in again after verification
      // Or keep them signed in and display a verification pending screen.
      // await FirebaseAuth.instance.signOut();
    }
  } on FirebaseAuthException catch (e) {
    if (e.code == 'weak-password') {
      print('The password provided is too weak.');
    } else if (e.code == 'email-already-in-use') {
      print('The account already exists for that email.');
    } else {
      print('Error during registration: ${e.message}');
    }
  } catch (e) {
    print(e);
  }
}

This function creates a user account and then immediately calls user.sendEmailVerification(). Firebase automatically handles sending an email to the provided address with a verification link.

Step 4: Handling Verification Status and User Experience

After sending the verification email, your application needs to guide the user to check their inbox. You'll also need a mechanism to detect when the user has successfully verified their email.

Firebase Authentication keeps track of the verification status in the User object's emailVerified property. However, this property is not updated in real-time within your app after the user clicks the verification link externally. You need to explicitly reload the user's data.

A common approach is to show a "Verification Pending" screen and provide a button for the user to indicate they have verified their email. When this button is pressed (or periodically), reload the user's data and check the emailVerified status.


class AuthWrapper extends StatefulWidget {
  @override
  _AuthWrapperState createState() => _AuthWrapperState();
}

class _AuthWrapperState extends State<AuthWrapper> {
  User? _user;

  @override
  void initState() {
    super.initState();
    FirebaseAuth.instance.authStateChanges().listen((User? user) {
      setState(() {
        _user = user;
      });
      if (user != null && !user.emailVerified) {
        // Start polling for email verification status, or wait for user action
        _checkEmailVerification(); // Initial check after auth state changes
      }
    });
  }

  void _checkEmailVerification() async {
    // Reload the user data to get the latest email verification status
    await _user!.reload();
    _user = FirebaseAuth.instance.currentUser; // Get updated user object

    if (_user!.emailVerified) {
      print("Email successfully verified!");
      // Navigate to the main application content or refresh UI
      setState(() {}); // Rebuild to show HomeScreen
    } else {
      print("Email not yet verified. Please check your inbox.");
      // You could set a timer to re-check after a few seconds,
      // or rely on a "I have verified my email" button.
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_user == null) {
      // User is not logged in
      return LoginScreen(onRegister: (email, password) async {
        await registerUserAndSendVerification(email, password);
        // After registration, Firebase will automatically sign in the user.
        // authStateChanges will then trigger, leading to the VerificationPendingScreen.
      });
    } else if (!_user!.emailVerified) {
      // User is logged in but email is not verified
      return VerificationPendingScreen(
        user: _user!,
        onReload: _checkEmailVerification,
        onResend: () async {
          await _user!.sendEmailVerification();
          print("Verification email re-sent!");
        },
      );
    } else {
      // User is logged in and email is verified
      return HomeScreen(); // Your main application screen
    }
  }
}

// Placeholder Widgets for demonstration
class LoginScreen extends StatelessWidget {
  final Function(String, String) onRegister;
  LoginScreen({required this.onRegister});

  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login / Register')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(controller: _emailController, decoration: InputDecoration(labelText: 'Email')),
            TextField(controller: _passwordController, obscureText: true, decoration: InputDecoration(labelText: 'Password')),
            ElevatedButton(
              onPressed: () {
                onRegister(_emailController.text, _passwordController.text);
              },
              child: Text('Register'),
            ),
          ],
        ),
      ),
    );
  }
}

class VerificationPendingScreen extends StatelessWidget {
  final User user;
  final VoidCallback onReload;
  final VoidCallback onResend;

  VerificationPendingScreen({required this.user, required this.onReload, required this.onResend});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Verify Your Email')),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'A verification email has been sent to ${user.email}. Please check your inbox and spam folder.',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: onReload,
                child: Text('I have verified my email'),
              ),
              SizedBox(height: 10),
              TextButton(
                onPressed: onResend,
                child: Text('Resend verification email'),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () async {
                  await FirebaseAuth.instance.signOut();
                },
                child: Text('Sign Out'),
                style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Welcome!')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('You are successfully logged in and your email is verified.'),
            ElevatedButton(
              onPressed: () async {
                await FirebaseAuth.instance.signOut();
              },
              child: Text('Sign Out'),
            ),
          ],
        ),
      ),
    );
  }
}

In this example:

  • AuthWrapper listens to authentication state changes.
  • If a user is logged in but their email is not verified, it shows a VerificationPendingScreen.
  • VerificationPendingScreen provides options to reload the user's status or resend the email.
  • The _checkEmailVerification method is crucial; it calls _user!.reload() to fetch the latest user data from Firebase, then checks _user!.emailVerified.

Important Considerations:

  • User Experience: Clearly instruct users to check their email (including spam folders). Provide options to resend the email or sign out.
  • Polling vs. Manual Reload: While periodic polling is possible, it's often better to rely on a user action ("I have verified my email") to trigger the reload, as constant polling consumes resources.
  • Email Templates: You can customize the verification email template from the Firebase Console under "Authentication" -> "Templates".
  • Error Handling: Implement robust error handling for all Firebase operations to provide meaningful feedback to users.

Conclusion

Implementing automatic email verification with Flutter and Firebase Authentication is a straightforward yet powerful way to enhance the security and integrity of your application's user base. By following the steps outlined in this article, you can efficiently set up the registration flow, send verification emails, and gracefully handle the verification status, leading to a more secure and reliable user experience.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is