Flutter Firebase Password Reset: Custom UI & Email Verification

31 Jul 2026

9K

35K

Flutter Firebase Password Reset: Custom UI & Email Verification

Implementing a secure and user-friendly password reset mechanism is a critical feature for any application. While Firebase Authentication provides a default password reset flow, customizing the user interface (UI) allows you to maintain brand consistency and offer a tailored experience. This article guides you through building a custom password reset flow in Flutter using Firebase Authentication, including steps for email verification and robust error handling.

By the end of this guide, you will understand how to:

  • Set up your Flutter project for Firebase Authentication.
  • Design a custom UI for initiating password resets.
  • Implement the logic to send password reset emails.
  • Enhance security by optionally checking email verification status.
  • Handle various success and error scenarios gracefully.

Why a Custom Password Reset Flow?

Firebase Authentication offers a default web-based password reset page. While functional, a custom UI provides several advantages:

  • Brand Consistency: Integrate the password reset experience seamlessly with your app's design language.
  • Improved User Experience: Keep users within your application's context, reducing friction.
  • Tailored Feedback: Provide specific, in-app messages for success, loading, or error states.
  • Additional Logic: Incorporate extra checks, like ensuring the user's email is verified before allowing a reset, for enhanced security.

Prerequisites

Before you begin, ensure you have the following:

  • A Flutter development environment set up.
  • A Firebase project created in the Firebase Console.
  • Firebase Authentication enabled for your project (specifically, the Email/Password sign-in method).
  • Your Flutter app connected to your Firebase project.

Step 1: Setting Up Your Flutter Project for Firebase Auth

First, add the necessary Firebase packages to your pubspec.yaml file:

dependencies:  flutter:    sdk: flutter  firebase_core: ^2.25.3  firebase_auth: ^4.17.4

Run flutter pub get to fetch the packages. Next, ensure Firebase is initialized in your main.dart file, typically within the main() function:

import 'package:firebase_core/firebase_core.dart';import 'package:flutter/material.dart';// Make sure you have your firebase_options.dart file generated// by running `flutterfire configure`import 'firebase_options.dart';Future<void> main() async {  WidgetsFlutterBinding.ensureInitialized();  await Firebase.initializeApp(    options: DefaultFirebaseOptions.currentPlatform,  );  runApp(const MyApp());}class MyApp extends StatelessWidget {  const MyApp({super.key});  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Password Reset Demo',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: const ResetPasswordScreen(),    );  }}

Step 2: Designing the Custom Password Reset UI

Create a simple UI with an email input field and a button to trigger the password reset email. You'll also need to display feedback to the user.

import 'package:flutter/material.dart';import 'package:firebase_auth/firebase_auth.dart';class ResetPasswordScreen extends StatefulWidget {  const ResetPasswordScreen({super.key});  @override  State<ResetPasswordScreen> createState() => _ResetPasswordScreenState();}class _ResetPasswordScreenState extends State<ResetPasswordScreen> {  final _emailController = TextEditingController();  final _formKey = GlobalKey<FormState>();  bool _isLoading = false;  String? _message;  Color _messageColor = Colors.black;  @override  void dispose() {    _emailController.dispose();    super.dispose();  }  Future<void> _sendPasswordResetEmail() async {    if (!_formKey.currentState!.validate()) {      return;    }    setState(() {      _isLoading = true;      _message = null;      _messageColor = Colors.black;    });    // Logic to send reset email will go here    // For now, let's simulate a delay    await Future.delayed(const Duration(seconds: 2));    setState(() {      _isLoading = false;      _message = 'Password reset email sent to ${_emailController.text}';      _messageColor = Colors.green;    });  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: const Text('Reset Password'),      ),      body: Padding(        padding: const EdgeInsets.all(16.0),        child: Form(          key: _formKey,          child: Column(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              TextFormField(                controller: _emailController,                keyboardType: TextInputType.emailAddress,                decoration: const InputDecoration(                  labelText: 'Email',                  border: OutlineInputBorder(),                ),                validator: (value) {                  if (value == null || value.isEmpty) {                    return 'Please enter your email';                  }                  if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {                    return 'Please enter a valid email address';                  }                  return null;                },              ),              const SizedBox(height: 20),              _isLoading                ? const CircularProgressIndicator()                : ElevatedButton(                    onPressed: _sendPasswordResetEmail,                    style: ElevatedButton.styleFrom(                      padding: const EdgeInsets.symmetric(                        horizontal: 40,                        vertical: 15,                      ),                    ),                    child: const Text(                      'Send Reset Email',                      style: TextStyle(fontSize: 18),                    ),                  ),              if (_message != null)                Padding(                  padding: const EdgeInsets.only(top: 20),                  child: Text(                    _message!,                    style: TextStyle(color: _messageColor, fontSize: 16),                    textAlign: TextAlign.center,                  ),                ),            ],          ),        ),      ),    );  }}

Step 3: Implementing the Password Reset Logic

Now, integrate Firebase Authentication to send the password reset email. The core method is FirebaseAuth.instance.sendPasswordResetEmail().

Update the _sendPasswordResetEmail method in _ResetPasswordScreenState:

// ... (previous code)Future<void> _sendPasswordResetEmail() async {  if (!_formKey.currentState!.validate()) {    return;  }  setState(() {    _isLoading = true;    _message = null;    _messageColor = Colors.black;  });  try {    await FirebaseAuth.instance.sendPasswordResetEmail(      email: _emailController.text.trim(),    );    setState(() {      _message = 'Password reset email sent to ${_emailController.text}. Please check your inbox.';      _messageColor = Colors.green;    });  } on FirebaseAuthException catch (e) {    String errorMessage;    switch (e.code) {      case 'invalid-email':        errorMessage = 'The email address is not valid.';        break;      case 'user-not-found':        errorMessage = 'No user found for that email. Please check the email address.';        break;      case 'too-many-requests':        errorMessage = 'Too many requests. Please try again later.';        break;      default:        errorMessage = 'An unknown error occurred: ${e.message}';    }    setState(() {      _message = errorMessage;      _messageColor = Colors.red;    });  } catch (e) {    setState(() {      _message = 'An unexpected error occurred: $e';      _messageColor = Colors.red;    });  } finally {    setState(() {      _isLoading = false;    });  }}// ... (rest of the code)

This code attempts to send the email and catches common FirebaseAuthException errors, providing user-friendly messages. It's crucial to handle these exceptions to guide the user effectively.

Step 4: Enhancing Security with Email Verification (Optional but Recommended)

To prevent unauthorized password reset attempts, you might want to ensure that the email address requesting a reset is actually verified. This adds an extra layer of security, as only users with a verified email can proceed.

To implement this, you would typically check the email verification status during the login process or when a user tries to access certain features. For a password reset flow, you could check if the email corresponds to an existing user whose email is verified before sending the reset link. However, Firebase's sendPasswordResetEmail method does not directly offer a pre-check for email verification status. It will send a reset email if a user with that email exists, regardless of their verification status.

If you want to enforce email verification before a reset, you would need a more complex flow, potentially involving a Cloud Function to first check user status. For most applications, relying on Firebase's built-in security for password resets (which includes rate limiting and unique, time-sensitive links) is sufficient. The primary purpose of email verification is often for initial account activation and access to features, not necessarily as a prerequisite for password resets. If a user can't access their email to verify it, they also can't access it to receive a reset link.

Step 5: Handling the Password Reset Link

When a user receives and clicks the password reset link in their email, Firebase typically directs them to a web page where they can enter and confirm a new password. This page can be customized in the Firebase Console under Authentication > Templates > Password reset. You can brand this page to match your application's look and feel.

For a fully in-app experience, you would need to implement Firebase Dynamic Links to intercept the reset link and handle it within your Flutter app. This is a more advanced topic and involves:

  1. Configuring Firebase Dynamic Links in your Firebase project.
  2. Setting up your Flutter app to handle incoming dynamic links.
  3. Parsing the dynamic link to extract the oobCode (out-of-band code) provided by Firebase.
  4. Using FirebaseAuth.instance.confirmPasswordReset(code: oobCode, newPassword: newPassword) to complete the reset within your app.

For simplicity and common use cases, relying on Firebase's web-based reset page is often sufficient and easier to implement.

Best Practices and Common Pitfalls

  • Input Validation: Always validate user input (e.g., email format) on the client side before sending it to Firebase.
  • Clear Feedback: Provide immediate and clear feedback to the user, whether the email was sent, if there was an error, or if the process is loading.
  • Error Handling: Catch specific FirebaseAuthException codes to provide meaningful error messages instead of generic ones.
  • Security: Be mindful of displaying too much information in error messages (e.g., confirming whether an email exists). Firebase's user-not-found error is designed to be ambiguous to prevent enumeration attacks.
  • Firebase Console Settings: Customize your password reset email template in the Firebase Console (Authentication > Templates) to include your app's branding and a helpful message.

Conclusion

You've successfully learned how to build a custom password reset flow in your Flutter application using Firebase Authentication. By creating a custom UI and implementing robust error handling, you can provide a seamless and secure experience for your users when they need to regain access to their accounts. Remember to always prioritize clear communication and security in your implementation.

Your next step could be to explore how to integrate Firebase Dynamic Links for a fully in-app password reset experience, or to further customize your Firebase email templates to enhance user trust and branding.

FAQ

Can I customize the content of the password reset email?Yes, you can customize the subject, sender name, and body of the password reset email directly from the Firebase Console under Authentication > Templates > Password reset. You can also include your app's logo and link back to your app.What happens if the user enters an email that doesn't exist?Firebase's sendPasswordResetEmail method will throw a FirebaseAuthException with the code user-not-found. It's best practice to display a message like "No user found for that email. Please check the email address." to the user.Is there a limit to how many password reset emails can be sent?Yes, Firebase Authentication includes built-in rate limiting to prevent abuse and spam. If too many requests are made from the same IP address or for the same email, Firebase might temporarily block further requests and return a too-many-requests error.Do I need to store user passwords in my database?No, with Firebase Authentication, Firebase handles the secure storage and management of user passwords. You should never store raw passwords in your own database.

Related Articles

Jul 31, 2026

Flutter Firebase Password Reset: Custom UI & Email Verification

Implement a robust password reset flow in Flutter using Firebase Authentication. Learn to build a custom UI, handle email verification, and secure user accounts

Jul 31, 2026

Build a Flutter Task Tracker Widget: Daily, Weekly, Monthly Views

Learn to build a dynamic task tracker widget in Flutter, featuring daily, weekly, and monthly views. This guide provides step-by-step instructions, code example

Jul 31, 2026

Flutter Layout Tips: Flexible UI with Wrap, Flow, and Spacer

Master Flutter's Wrap, Flow, and Spacer widgets to build highly flexible and responsive user interfaces. Learn practical tips and examples for dynamic content a