Flutter - Designing Email Authentication System using Firebase
Last Updated :
04 May, 2025
Flutter is an amazing tool for developing cross-platform applications using a single code base. While Flutter is useful, it gets even better when you add Firebase. In this article, we'll discuss how to implement the Email/Password Authentication process in Flutter, using Firebase.
In this article, we'll cover the following Flutter development aspects:
- Improved widget tree.
- TextFormField Validation logic.
- Toggle password text visibility.
- Handling Authentication errors in UI.
- Custom submit button with loading state.
- UI logic and Firebase authentication logic, separated.
Note: Configure Firebase in your Flutter application, before diving into Firebase Authentication. Check this link for the initial firebase setup with flutter.
Project Preview
Now, let's look into the implementation.
Step-by-Step Implementation
Step 1: Installing and Initializing Firebase
Installation:
Before any Firebase services can be used, you must first install the firebase_core plugin, which is responsible for connecting your application to Firebase. Add the plugin to your pubspec.yaml file. Also, add a few supporting plugins which is used to deal with the authentication flow.
project/lib/pubspec.yaml
dependencies:
flutter:
sdk: flutter
firebase_core: "0.5.2"
firebase_auth: "^0.18.3"
provider: ^4.3.2+2
cloud_firestore: ^0.14.3
font_awesome_flutter: ^8.10.0
Install the plugin by running the following command from the project root:
$ flutter pub get
Initializing Firebase
To initialize Firebase, call the .initializeApp() method on the Firebase class, as described in the below code. This method is asynchronous and returns a Future, so we need to ensure it has completed before displaying our main application. Updating the main( ) method of main.dart
project/lib/main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
Step 2: Folder Structure
Following below folder structure to process forward.
Step 2: Firebase Authentication Service
Authentication Process:
/libservices/authentication_service.dart
Create a new folder as services, and inside it a new dart file as authentication_service.dart, it will contain all the authentication logic, which in case help us to separate the UI logic.
authentication_service.dart
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../libmodels/user_model.dart';
class AuthenticationService {
final FirebaseAuth _firebaseAuth;
UserModel userModel = UserModel(
email: '',
uid: '',
username: '',
timestamp: DateTime.now(),
);
final userRef = FirebaseFirestore.instance.collection("users");
AuthenticationService(this._firebaseAuth);
// managing the user state via stream.
// stream provides an immediate event of
// the user's current authentication state,
// and then provides subsequent events whenever
// the authentication state changes.
Stream<User?> get authStateChanges => _firebaseAuth.authStateChanges();
//1
Future<String> signIn({
required String email,
required String password,
}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
return "Signed In";
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
return "No user found for that email.";
} else if (e.code == 'wrong-password') {
return "Wrong password provided for that user.";
} else {
return "Something Went Wrong.";
}
}
}
//2
Future<String> signUp({
required String email,
required String password,
}) async {
try {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return "Signed Up";
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
return "The password provided is too weak.";
} else if (e.code == 'email-already-in-use') {
return "The account already exists for that email.";
} else {
return "Something Went Wrong.";
}
} catch (e) {
print(e);
return "An unexpected error occurred.";
}
}
Future<void> addUserToDB({
required String uid,
required String username,
required String email,
required DateTime timestamp,
}) async {
try {
// Creating the userModel object
final userModel = UserModel(
uid: uid,
username: username,
email: email,
timestamp: timestamp,
);
// Storing user data in Firestore
await userRef.doc(uid).set(userModel.toMap());
print("successfully added user to DB");
} catch (e) {
// Log the error or show a user-friendly message
print('Error adding user to DB: $e');
rethrow;
}
}
//4
Future<UserModel> getUserFromDB({required String uid}) async {
final DocumentSnapshot doc = await userRef.doc(uid).get();
//print(doc.data());
return UserModel.fromMap(doc.data() as Map<String, dynamic>);
}
//5
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
}
/libmodels/user_model.dart
Dart
class UserModel {
final String uid;
final String username;
final String email;
final DateTime timestamp;
UserModel({
required this.uid,
required this.username,
required this.email,
required this.timestamp,
});
// Convert UserModel instance to Map
Map<String, dynamic> toMap() {
return {
'uid': uid,
'username': username,
'email': email,
'timestamp':
timestamp.toIso8601String(), // Convert DateTime to string if needed
};
}
// Convert Map to UserModel instance
factory UserModel.fromMap(Map<String, dynamic> map) {
return UserModel(
uid: map['uid'],
username: map['username'],
email: map['email'],
timestamp: DateTime.parse(map['timestamp']),
);
}
}
The user_model.dart is just a Plain Old Dart Object Strategy, which is used to convert the user model into a Map and also to retrieve it as a Map like it's a JSON object basically.
The authentication_service.dart file:
Below are the methods used in the authentication_service.dart file:
1. signIn({String email, String password})
- This method accepts email and password as a parameter.
- Email and Password are used to Sign-In the user.
- It returns an appropriate message when the user "Signed In".
- It returns an appropriate message when FirebaseAuthException catches an error.
2. signUp({String email, String password})
- This method accepts email and password as a parameter.
- Email and Password are used to Register the user.
- It returns an appropriate message when the user "Signed Up” .
- It returns an appropriate message when FirebaseAuthException catches an error.
3. addUserToDB({String uid, String username, String email, DateTime timestamp})
- This method accepts user uid, username, email, and the current timestamp( DateTime.now( ) ) of the user as a parameter.
- Creating a new document of the current user in the Cloud Firestore Database. (Do enable cloud firestore from your firebase project console). The document name should be the uid of the user.
- This method can only be triggered, when the current user is a new user, i.e the user registered in our application at the initial point.
4. getUserFromDB({String uid})
- This method accepts the current user uid as a parameter.
- It will help us to get the current user, stored data from the cloud firestore database.
5. signOut()
- This method is simply used to signing out the user from the application.
Step 4 : Checking the Authentication State
Registering the AuthenticationService methods as Provider, in our main.dart file.
Project/lib/main.dart
main.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_geeks/libservices/authentication_service.dart';
import 'package:flutter_geeks/pages/home_page.dart';
import 'package:flutter_geeks/pages/login_page.dart'; // Assuming you have a LoginPage.
import 'package:flutter_geeks/pages/register_page.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthenticationService>(
create: (_) => AuthenticationService(FirebaseAuth.instance),
),
StreamProvider<User?>(
create: (context) =>
context.read<AuthenticationService>().authStateChanges,
initialData: null,
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.green[400],
hintColor: Colors.deepOrange[200],
),
home: AuthenticationWrapper(),
),
);
}
}
class AuthenticationWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
final user = context.watch<User?>(); // Watch User?
if (user != null) {
// If user is logged in, navigate to HomePage
return HomePage();
} else {
// If user is not logged in, navigate to LoginPage
return RegisterPage(); // Assuming you have a LoginPage widget
}
}
}
The AuthenticationWrapper class of main.dart, checks the state of the user. If the user is not Logged-In it will display the AuthScreenView( ), If the user is Logged-In it will display the HomePage( ).
Step 5: Switching Between ScreenView's, Based On The Authentication State.
AuthScreenView( ):
/lib/pages/auth_screen_view.dart
AuthScreenView is just a pageview, which deals with switching between the LoginPage() and RegisterPage() .
auth_screen_view.dart
Dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_geeks/pages/login_page.dart';
import 'package:flutter_geeks/pages/register_page.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class AuthScreenView extends StatefulWidget {
@override
_AuthScreenViewState createState() => _AuthScreenViewState();
}
class _AuthScreenViewState extends State<AuthScreenView> {
late PageController pageController;
int pageIndex = 0;
@override
void initState() {
// TODO: implement initState
super.initState();
pageController = PageController();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
pageController.dispose();
}
onPageChanged(int pageIndex) {
setState(() {
this.pageIndex = pageIndex;
});
}
onTap(int pageIndex) {
//pageController.jumpToPage(pageIndex);
pageController.animateToPage(
pageIndex,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
children: [
//when pageIndex == 0
LoginPage(),
//when pageIndex == 1
RegisterPage(),
],
controller: pageController,
onPageChanged: onPageChanged,
),
bottomNavigationBar: CupertinoTabBar(
currentIndex: pageIndex,
onTap: onTap,
activeColor: Theme.of(context).primaryColor,
items: [
BottomNavigationBarItem(
label: "Log-In",
icon: Icon(FontAwesomeIcons.signInAlt),
),
BottomNavigationBarItem(
label: "Register",
icon: Icon(FontAwesomeIcons.userPlus),
),
],
),
);
}
}
RegisterPage( ):
/lib/pages/register_page.dart
register_page.dart:
Dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_geeks/libservices/authentication_service.dart';
import 'package:flutter_geeks/pages/login_page.dart';
import 'package:provider/provider.dart';
class RegisterPage extends StatefulWidget {
@override
_RegisterPageState createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
//To Toggle Password Text Visibility.
bool _obscureText = true;
late String _username, _email, _password;
//For the loading state.
bool _isSubmitting = false;
final _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
FirebaseAuth auth = FirebaseAuth.instance;
final DateTime timestamp = DateTime.now();
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: Text("GeeksForGeeks"), centerTitle: true),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Center(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: [
_showTitle(),
_showUsernameInput(),
_showEmailInput(),
_showPasswordInput(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("Already have an account?"),
TextButton(
child: Text("Login"),
style: TextButton.styleFrom(
foregroundColor: Colors.orange,
textStyle: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => LoginPage(),
),
);
},
),
],
),
_showFormActions(),
],
),
),
),
),
),
);
}
//1
_showTitle() {
return Text(
"Register",
style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
);
}
//2
_showUsernameInput() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: TextFormField(
onSaved: (val) => _username = val!,
validator: (val) => val!.length < 6 ? "Username is too short." : null,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Username",
hintText: "Enter Valid Username",
icon: Icon(Icons.face, color: Colors.grey),
),
),
);
}
//3
_showEmailInput() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: TextFormField(
onSaved: (val) => _email = val!,
validator: (val) => !val!.contains("@") ? "Invalid Email" : null,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Email",
hintText: "Enter Valid Email",
icon: Icon(Icons.mail, color: Colors.grey),
),
),
);
}
//4
_showPasswordInput() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: TextFormField(
onSaved: (val) => _password = val!,
validator: (val) => val!.length < 6 ? "Password Is Too Short" : null,
obscureText: _obscureText,
decoration: InputDecoration(
suffixIcon: GestureDetector(
onTap: () {
setState(() {
_obscureText = !_obscureText;
});
},
child: Icon(_obscureText ? Icons.visibility_off : Icons.visibility),
),
border: OutlineInputBorder(),
labelText: "Password",
hintText: "Enter Valid Password",
icon: Icon(Icons.lock, color: Colors.grey),
),
),
);
}
//5
_showFormActions() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: Column(
children: [
_isSubmitting == true
? CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(context).primaryColor,
),
)
: ElevatedButton(
child: Text(
"Submit",
style: TextStyle(color: Colors.black, fontSize: 18),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
onPressed: _submit,
),
],
),
);
}
//6
_submit() {
final _form = _formKey.currentState;
if (_form!.validate()) {
_form.save();
//print("Email $_email, Password $_password, Username $_username");
_registerUser();
} else {
print("Form is Invalid");
}
}
//7
_registerUser() async {
setState(() {
_isSubmitting = true;
});
final logMessage = await context.read<AuthenticationService>().signUp(
email: _email,
password: _password,
);
logMessage == "Signed Up"
? _showSuccessSnack(logMessage)
: _showErrorSnack(logMessage);
print("logMessage:$logMessage");
if (logMessage == "Signed Up") {
createUserInFirestore();
} else {
setState(() {
_isSubmitting = false;
});
}
}
//When User "Signed Up", success snack will display.
_showSuccessSnack(String message) {
final snackbar = SnackBar(
backgroundColor: Colors.black,
content: Text("$message", style: TextStyle(color: Colors.green)),
);
ScaffoldMessenger.of(context).showSnackBar(snackbar);
_formKey.currentState!.reset();
}
//When FirebaseAuth Catches error, error snack will display.
_showErrorSnack(String message) {
final snackbar = SnackBar(
backgroundColor: Colors.black,
content: Text("$message", style: TextStyle(color: Colors.red)),
);
ScaffoldMessenger.of(context).showSnackBar(snackbar);
}
createUserInFirestore() async {
context.read<AuthenticationService>().addUserToDB(
uid: auth.currentUser!.uid,
username: _username,
email: auth.currentUser!.email ?? '',
timestamp: timestamp,
);
}
}
Output:
LoginPage( ):
The LoginPage is exactly similar to the RegisterPage, the only difference is, the LoginPage is having only two TextFormField (For email and password) and while submitting it triggers signIn() method of authentication_service.dart
/lib /pages/login_page.dart
login_page.dart:
Dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_geeks/libservices/authentication_service.dart';
import 'package:flutter_geeks/pages/home_page.dart';
import 'package:flutter_geeks/pages/register_page.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
bool _obscureText = true;
late String _email, _password;
bool _isSubmitting = false;
final _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
FirebaseAuth auth = FirebaseAuth.instance;
final DateTime timestamp = DateTime.now();
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(centerTitle: true, title: Text("GeeksForGeeks")),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Center(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: [
_showTitle(),
_showEmailInput(),
_showPasswordInput(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("Don't have an account?"),
TextButton(
child: Text("Register"),
style: TextButton.styleFrom(
foregroundColor: Colors.orange,
textStyle: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => RegisterPage(),
),
);
},
),
],
),
_showFormActions(),
],
),
),
),
),
),
);
}
_showTitle() {
return Text(
"Login",
style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
);
}
_showEmailInput() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: TextFormField(
onSaved: (val) => _email = val!,
validator: (val) => !val!.contains("@") ? "Invalid Email" : null,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Email",
hintText: "Enter Valid Email",
icon: Icon(Icons.mail, color: Colors.grey),
),
),
);
}
_showPasswordInput() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: TextFormField(
onSaved: (val) => _password = val!,
validator: (val) => val!.length < 6 ? "Password Is Too Short" : null,
obscureText: _obscureText,
decoration: InputDecoration(
suffixIcon: GestureDetector(
onTap: () {
setState(() {
_obscureText = !_obscureText;
});
},
child: Icon(_obscureText ? Icons.visibility_off : Icons.visibility),
),
border: OutlineInputBorder(),
labelText: "Password",
hintText: "Enter Valid Password",
icon: Icon(Icons.lock, color: Colors.grey),
),
),
);
}
_showFormActions() {
return Padding(
padding: EdgeInsets.only(top: 20),
child: Column(
children: [
_isSubmitting == true
? CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(context).primaryColor,
),
)
: ElevatedButton(
child: Text(
"Submit",
style: TextStyle(color: Colors.black, fontSize: 18),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.black,
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
onPressed: _submit,
),
],
),
);
}
_submit() {
final _form = _formKey.currentState;
if (_form!.validate()) {
_form.save();
//print("Email $_email, Password $_password");
_LoginUser();
} else {
print("Form is Invalid");
}
}
_LoginUser() async {
setState(() {
_isSubmitting = true;
});
final logMessage = await context.read<AuthenticationService>().signIn(
email: _email,
password: _password,
);
logMessage == "Signed In"
? _showSuccessSnack(logMessage)
: _showErrorSnack(logMessage);
//print("I am logMessage $logMessage");
if (logMessage == "Signed In") {
// Navigate to HomePage after successful login
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder:
(context) => HomePage(), // Assuming you have a HomePage widget
),
);
} else {
setState(() {
_isSubmitting = false;
});
}
}
_showSuccessSnack(String message) async {
final snackbar = SnackBar(
backgroundColor: Colors.black,
content: Text("$message", style: TextStyle(color: Colors.green)),
);
ScaffoldMessenger.of(context).showSnackBar(snackbar);
_formKey.currentState!.reset();
}
_showErrorSnack(String message) {
final snackbar = SnackBar(
backgroundColor: Colors.black,
content: Text("$message", style: TextStyle(color: Colors.red)),
);
ScaffoldMessenger.of(context).showSnackBar(snackbar);
setState(() {
_isSubmitting = false;
});
}
}
Output:
HomePage:
The HomePage will be displayed when the firebaseUser != null, checking from main.dart
lib/pages/home_page.dart
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_geeks/libmodels/user_model.dart';
import 'package:flutter_geeks/libservices/authentication_service.dart';
import 'package:provider/provider.dart';
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
FirebaseAuth auth = FirebaseAuth.instance;
final userRef = FirebaseFirestore.instance.collection("users");
late UserModel _currentUser;
String _uid = "";
String _username = "";
String _email = "";
@override
void initState() {
// TODO: implement initState
super.initState();
getCurrentUser();
}
getCurrentUser() async {
UserModel currentUser = await context
.read<AuthenticationService>()
.getUserFromDB(uid: auth.currentUser!.uid);
_currentUser = currentUser;
print("${_currentUser.username}");
setState(() {
_uid = _currentUser.uid ?? "";
_username = _currentUser.username ?? "";
_email = _currentUser.email ?? "";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("HomePage"), centerTitle: true),
body:
_uid.isEmpty || _username.isEmpty || _email.isEmpty
? Center(child: CircularProgressIndicator())
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"uid is ${_uid} , email is ${_email}, name is ${_username}",
textAlign: TextAlign.center,
),
Center(
child: ElevatedButton(
child: Text(
"Logout",
style: TextStyle(color: Colors.black, fontSize: 18),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
onPressed: () {
context.read<AuthenticationService>().signOut();
},
),
),
],
),
);
}
}
Output:
Final Output:
Similar Reads
Flutter - Firebase GitHub Authentication GitHub is like a digital hub for code. Imagine it as a virtual space where developers team up to build amazing things. It stores and tracks changes to code, making it easy for everyone to collaborate, contribute, and create software together. It's the cool place where coding magic happens. Since Git
5 min read
Face Detection in Flutter using Firebase ML Kit Face detection is a technique by which we can locate the human faces in the image given. Face detection is used in many places nowadays, especially websites hosting images like Picasa, Photobucket, and Facebook. The automatic tagging feature adds a new dimension to sharing pictures among the people
5 min read
Flutter - Store User Details Using Firebase Firebase is a product of Google that helps developers to build, manage, and grow their apps easily. It helps developers to build their apps faster and in a more secure way. No programming is required on the Firebase side which makes it easy to use its features more efficiently. It provides services
3 min read
Django Authentication Project with Firebase Django is a Python-based web framework that allows you to quickly create efficient web applications.. When we are building any website, we will need a set of components: how to handle user authentication (signing up, signing in, signing out), a management panel for managing our website, how to uploa
7 min read
Firebase Authentication with Phone Number OTP in Flutter Web Phone number verification using Firebase in the Flutter Web app can be done by sending OTP SMS to the phone number. The user will enter the OTP in the message and will easily sign in to his/her account. We are going to implement it in Flutter Web. Step by Step implementation Step 1: Create a new Flu
4 min read