Flutter & Firebase Auth: Automating Email Verification
Email verification is a critical security measure for any application, ensuring that users provide a valid email address and preventing unauthorized access or spam. When building Flutter applications, Firebase Authentication provides a robust and straightforward way to implement this feature. This article will guide you through integrating Firebase Authentication in your Flutter app to automatically send and manage email verification.
Prerequisites
- A Flutter project set up and running.
- A Firebase project configured for your Flutter application.
- Firebase Authentication enabled (specifically Email/Password provider) in your Firebase console.
1. Setting Up Firebase in Your Flutter Project
First, ensure you have the necessary Firebase packages installed in your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
firebase_core: ^latest_version
firebase_auth: ^latest_version
After adding these, run flutter pub get. Then, initialize Firebase in your main.dart file, typically within the main function:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Firebase Email Verify',
home: AuthWrapper(), // We'll create this later
);
}
}
2. User Registration and Sending Verification Email
The core idea is to send an email verification link immediately after a user successfully registers. Firebase Auth provides a dedicated method for this.
Here's an example of a registration function:
import 'package:firebase_auth/firebase_auth.dart';
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}');
// You might want to show a SnackBar or AlertDialog to the user
// indicating that an email has been sent.
}
} 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);
}
}
After calling user.sendEmailVerification(), Firebase automatically sends an email to the registered address containing a link. When the user clicks this link, their email verification status is updated in Firebase.
3. Checking Email Verification Status
Once the user has registered and potentially clicked the verification link, your application needs a way to check their verification status. This is crucial for controlling access to certain features.
The key is to use currentUser?.emailVerified. However, it's important to know that after a user verifies their email (e.g., by clicking a link outside the app), the emailVerified property within your app's current User object won't automatically update. You need to explicitly reload the user's data:
import 'package:firebase_auth/firebase_auth.dart';
Future<bool> checkIfEmailIsVerified() async {
User? user = FirebaseAuth.instance.currentUser;
if (user != null) {
// Reload the user to get the most up-to-date status
await user.reload();
user = FirebaseAuth.instance.currentUser; // Get the reloaded user object
return user?.emailVerified ?? false;
}
return false;
}
You can use this function to determine what content to show the user. For instance, redirect them to a "verify your email" screen if not verified, or to the main app dashboard if they are.
4. Handling Authentication State Changes
For a dynamic user experience, you should listen to authentication state changes. This allows your app to react in real-time when a user logs in, logs out, or crucially, when their email verification status changes (after a reload). The authStateChanges() stream is perfect for this.
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class AuthWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator(); // Or a splash screen
}
if (snapshot.hasData) {
final user = snapshot.data!;
// For simplicity, we assume the user object from authStateChanges
// might not be reloaded immediately after external verification.
// A dedicated screen will handle the reload and check.
if (!user.emailVerified) {
return EmailVerificationScreen();
} else {
return HomeScreen();
}
}
return LoginScreen(); // Or RegistrationScreen
},
);
}
}
// Example EmailVerificationScreen
class EmailVerificationScreen extends StatefulWidget {
@override
_EmailVerificationScreenState createState() => _EmailVerificationScreenState();
}
class _EmailVerificationScreenState extends State<EmailVerificationScreen> {
User? _user;
bool _isVerifying = false;
@override
void initState() {
super.initState();
_user = FirebaseAuth.instance.currentUser;
// Periodically check or provide a button to check
_checkEmailVerifiedPeriodically();
}
void _checkEmailVerifiedPeriodically() {
// A simple timer to check verification status every few seconds.
// In a real app, consider a "Refresh" button or more sophisticated polling.
Future.delayed(Duration(seconds: 3), () async {
if (!mounted) return;
if (_user != null) {
await _user!.reload(); // Get the latest user data from Firebase
_user = FirebaseAuth.instance.currentUser; // Update the local user object
if (_user!.emailVerified) {
// If verified, navigate away (e.g., to home screen)
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => HomeScreen()));
} else {
_checkEmailVerifiedPeriodically(); // Check again
}
}
});
}
Future<void> _resendVerificationEmail() async {
setState(() { _isVerifying = true; });
try {
await _user?.sendEmailVerification();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Verification email sent! Please check your inbox.')),
);
} catch (e) {
print('Error resending verification email: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to resend email.')),
);
} finally {
setState(() { _isVerifying = false; });
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Verify Your Email')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.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: _isVerifying ? null : _resendVerificationEmail,
child: _isVerifying ? CircularProgressIndicator(color: Colors.white) : Text('Resend Email'),
),
SizedBox(height: 10),
TextButton(
onPressed: () {
// Option to sign out if they can't verify
FirebaseAuth.instance.signOut();
},
child: Text('Sign Out'),
),
],
),
),
),
);
}
}
// Dummy screens for navigation example
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text('Login')), body: Center(child: Text('Login Page')));
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome, you are verified!', style: TextStyle(fontSize: 18)),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signOut();
},
child: Text('Sign Out'),
),
],
),
),
);
}
}
The EmailVerificationScreen demonstrates a common pattern: informing the user, providing a "Resend Email" option, and periodically checking their verification status by calling user.reload().
Conclusion
Automating email verification with Flutter and Firebase Authentication is a straightforward yet powerful way to enhance the security and integrity of your application. By sending verification emails immediately upon registration and implementing robust checks for the emailVerified status, you ensure that your users have confirmed their email addresses, leading to a more reliable and trustworthy user base. Remember to guide your users clearly through the verification process and provide options for resending emails to improve their overall experience.