Flutter & Firebase Auth: Implementing Email Verification and Reset Password
User authentication is a cornerstone of most modern applications. Firebase Authentication provides a robust, secure, and easy-to-implement solution for managing users in Flutter applications. Beyond basic sign-up and sign-in, two critical features for enhancing user security and experience are email verification and password reset. This article will guide you through implementing these features using Flutter and Firebase Auth.
1. Prerequisites and Project Setup
1.1. Flutter Project Creation
Ensure you have Flutter SDK installed. Create a new Flutter project:
flutter create flutter_firebase_auth_demo
cd flutter_firebase_auth_demo
1.2. Firebase Project Setup
- Go to the Firebase Console and create a new project.
- Enable Email/Password sign-in method in the Firebase Authentication section (Authentication > Sign-in method > Email/Password).
- Register your Android and iOS apps with the Firebase project, following the instructions to download configuration files (
google-services.jsonfor Android,GoogleService-Info.plistfor iOS) and place them in the correct directories.
1.3. Integrating Firebase with Flutter
Add Dependencies
Open your pubspec.yaml file and add the following dependencies:
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.24.2 # Use the latest version
firebase_auth: ^4.15.2 # Use the latest version
Run flutter pub get to fetch the packages.
Initialize Firebase
In your main.dart file, initialize Firebase before running your app:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); // Initialize Firebase
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Firebase Auth',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AuthScreen(), // Your main authentication screen
);
}
}
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State createState() => _AuthScreenState();
}
class _AuthScreenState extends State {
// Placeholder for your authentication UI
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Auth Demo')),
body: Center(
child: Text('Welcome to Auth Demo!'),
),
);
}
}
2. User Authentication (Registration & Login)
2.1. User Registration
First, let's implement user registration. This will create a new user account in Firebase Auth.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
// ... (inside your AuthScreenState or a dedicated auth service)
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> registerUser(String email, String password) async {
try {
UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
print('User registered: ${userCredential.user?.email}');
// Immediately send verification email after registration
await userCredential.user?.sendEmailVerification();
print('Verification email sent to ${userCredential.user?.email}');
// You might want to navigate to a "Check Your Email" screen here
} 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(e.message);
}
} catch (e) {
print(e);
}
}
2.2. User Login
Users need to log in to access the application. We will check their email verification status after login.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
// ... (inside your AuthScreenState or a dedicated auth service)
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<User?> signInUser(String email, String password) async {
try {
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
print('User logged in: ${userCredential.user?.email}');
return userCredential.user;
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
} else {
print(e.message);
}
return null;
} catch (e) {
print(e);
return null;
}
}
3. Implementing Email Verification
Email verification ensures that the email address provided by the user actually belongs to them. This is crucial for security and communication.
3.1. Sending Verification Email
As shown in the registration example, you can send an email verification link immediately after a user registers:
// After user registration or if a user needs to resend verification
User? user = _auth.currentUser;
if (user != null && !user.emailVerified) {
try {
await user.sendEmailVerification();
print('Verification email sent.');
} catch (e) {
print('Error sending verification email: $e');
}
}
Firebase will send an email with a unique link to the user's registered email address. When the user clicks this link, their emailVerified status in Firebase will be updated to true.
3.2. Checking Verification Status
After a user clicks the verification link, their client-side User object might not immediately reflect the updated emailVerified status. You need to reload the user's profile:
// Call this function periodically or when the user tries to perform a restricted action
Future<bool> checkEmailVerificationStatus() async {
User? user = _auth.currentUser;
if (user != null) {
await user.reload(); // Reloads the user's profile from Firebase
user = _auth.currentUser; // Get the reloaded user object
if (user != null && user.emailVerified) {
print('Email is verified!');
return true;
} else {
print('Email is NOT verified.');
return false;
}
}
return false;
}
3.3. UI Considerations for Verification
You should guide users through the verification process. For example:
- After registration, navigate to a screen that informs the user to check their email.
- On subsequent logins, if
user.emailVerifiedisfalse, display a message and a "Resend Verification Email" button. - Provide a "I've Verified My Email" button that calls
checkEmailVerificationStatus()to update the UI without requiring a full re-login.
// Example UI logic snippet (inside a StatefulWidget)
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
User? _currentUser;
bool _isEmailVerified = false;
@override
void initState() {
super.initState();
_currentUser = FirebaseAuth.instance.currentUser;
_isEmailVerified = _currentUser?.emailVerified ?? false;
// Optional: Listener for auth state changes, though reload() is more direct for verification status
// FirebaseAuth.instance.authStateChanges().listen((User? user) {
// if (user != null) {
// setState(() {
// _currentUser = user;
// _isEmailVerified = user.emailVerified;
// });
// }
// });
}
Future<void> _checkVerificationAndRefresh() async {
User? user = FirebaseAuth.instance.currentUser;
if (user != null) {
await user.reload();
setState(() {
_isEmailVerified = FirebaseAuth.instance.currentUser?.emailVerified ?? false;
});
if (_isEmailVerified) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Email verified successfully!')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Email still not verified. Check your inbox.')),
);
}
}
}
Future<void> _sendVerificationEmail() async {
User? user = FirebaseAuth.instance.currentUser;
if (user != null) {
try {
await user.sendEmailVerification();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Verification email sent! Check your inbox.')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error sending verification email: $e')),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: _currentUser == null
? const Text('Please log in.')
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Logged in as: ${_currentUser!.email}'),
if (!_isEmailVerified)
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text(
'Your email is not verified. Please check your inbox for a verification link.',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.red),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: _sendVerificationEmail,
child: const Text('Resend Verification Email'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: _checkVerificationAndRefresh,
child: const Text('I\'ve Verified My Email'),
),
],
),
),
if (_isEmailVerified)
const Text(
'Email Verified! You can access all features.',
style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
await FirebaseAuth.instance.signOut();
// Navigate back to login screen
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const AuthScreen()));
},
child: const Text('Sign Out'),
),
],
),
),
);
}
}
4. Implementing Password Reset
Allowing users to reset their forgotten passwords is a standard and essential feature. Firebase Auth handles the email sending and link validation.
4.1. Sending Password Reset Email
To send a password reset email, you only need the user's email address:
import 'package:firebase_auth/firebase_auth.dart';
// ... (inside your AuthScreenState or a dedicated auth service)
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> sendPasswordResetEmail(String email) async {
try {
await _auth.sendPasswordResetEmail(email: email);
print('Password reset email sent to $email');
// Inform the user that the email has been sent
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else {
print(e.message);
}
} catch (e) {
print(e);
}
}
Firebase sends an email with a unique link. When the user clicks this link, they will be redirected to a page (either a Firebase-hosted page or your custom page, if configured) where they can enter and confirm a new password.
4.2. UI for Password Reset Request
You typically provide a "Forgot Password?" link on your login screen. When tapped, it should navigate to a dedicated screen or show a dialog where the user can enter their email address.
// Example UI for a "Forgot Password" screen or dialog
class ForgotPasswordScreen extends StatefulWidget {
const ForgotPasswordScreen({super.key});
@override
State<ForgotPasswordScreen> createState() => _ForgotPasswordScreenState();
}
class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
final TextEditingController _emailController = TextEditingController();
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> _resetPassword() async {
try {
await _auth.sendPasswordResetEmail(email: _emailController.text.trim());
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Password reset email sent! Check your inbox.')),
);
Navigator.of(context).pop(); // Go back to login screen
} on FirebaseAuthException catch (e) {
String message;
if (e.code == 'user-not-found') {
message = 'No user found for that email.';
} else if (e.code == 'invalid-email') {
message = 'The email address is not valid.';
} else {
message = e.message ?? 'An unknown error occurred.';
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Reset Password')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text('Enter your email to receive a password reset link:'),
const SizedBox(height: 20),
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _resetPassword,
child: const Text('Send Reset Link'),
),
],
),
),
);
}
}
5. Best Practices and Conclusion
- Error Handling: Always implement robust error handling using
try-catchblocks for Firebase operations to provide meaningful feedback to users. - UI Feedback: Show loading indicators, success messages, and error messages to improve user experience.
- State Management: For larger applications, consider using a state management solution (Provider, BLoC, Riverpod, GetX) to manage authentication states and user data more effectively.
- Security Rules: While not directly covered here, remember to set up Firebase security rules for any Firestore/Realtime Database data to ensure only authenticated and authorized users can access specific resources.
- Customization: You can customize the templates for email verification and password reset emails from the Firebase Console (Authentication > Templates).
By implementing email verification and password reset, you significantly enhance the security and usability of your Flutter application. Firebase Authentication simplifies these complex processes, allowing you to focus on building great features for your users.