Authentication is one of the most important features of any app. Users expect to sign up, log in, and stay logged in securely. In Flutter, the easiest way to handle authentication is by using Firebase Authentication.

I spent two weeks trying to build my own authentication system for a practice app—hashing passwords, managing sessions, securing tokens. It was a nightmare. Then I switched to Firebase Authentication and had login working in 30 minutes. That’s when I understood why every Flutter developer recommends it.

This tutorial walks you through setting up email and password authentication in Flutter using Firebase Authentication.

Tip: Authentication seems scary at first, but Firebase makes it surprisingly simple. Focus on getting the basics working first—email and password login. You can add Google Sign-In and other methods later once you understand the fundamentals.

What Is Firebase Authentication?

Firebase Authentication is a service that lets you add secure login functionality to your app without writing backend code. According to Firebase’s official documentation, Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users.

What Firebase handles for you:

User Registration: Email validation, password strength checking, duplicate detection

Login and Sessions: Credential verification, token generation, session persistence

Security: Password hashing, secure token management, HTTPS encryption

User Management: Password reset emails, email verification, account deletion

For my Flutter practice projects, Firebase Authentication means I can focus on building features instead of worrying about security vulnerabilities.

Prerequisites

Before starting, make sure you have:

Firebase already set up in your Flutter app – If not, complete our How to Set Up Firebase in Flutter tutorial first.

Flutter app running without errors – Verify with flutter run

Basic Flutter knowledge – Understanding of StatefulWidget and async/await. Check our Flutter Introduction and Core Concepts if needed.

What We’re Building

In this tutorial, we’ll create:

  • Sign up functionality for new users
  • Sign in functionality for existing users
  • Current user detection
  • Sign out functionality
  • Authentication state listening

Step 1: Enable Email & Password Authentication

Before writing code, enable authentication in your Firebase project.

1.1 Open Firebase Console

Go to Firebase Console and select your project.

1.2 Navigate to Authentication

Click Authentication in the left sidebar, then click Get started.

Firebase Console with Authentication section highlighted

1.3 Enable Email/Password

  1. Click the Sign-in method tab
  2. Find Email/Password in the list
  3. Toggle Enable to ON
  4. Click Save
Email/Password enable toggle

Your Firebase backend is now ready to handle authentication.


Step 2: Add Firebase Authentication Package

2.1 Open pubspec.yaml

Add firebase_auth under dependencies:

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.24.2
  firebase_auth: ^4.16.0

Version note: Always check pub.dev/packages/firebase_auth for the latest version in 2026. The authentication logic remains the same across versions.

2.2 Install the Package

flutter pub get

Step 3: Import and Initialize Firebase Auth

3.1 Import Firebase Auth

import 'package:firebase_auth/firebase_auth.dart';

3.2 Create FirebaseAuth Instance

final FirebaseAuth _auth = FirebaseAuth.instance;

Place this at the top of your widget class. This instance provides access to all authentication methods.


Step 4: Create User Registration (Sign Up)

4.1 Sign Up Function

Future<void> signUp(String email, String password) async {
  try {
    UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
      email: email,
      password: password,
    );
    print("User registered: ${userCredential.user?.email}");
  } on FirebaseAuthException catch (e) {
    if (e.code == 'weak-password') {
      print('Password is too weak (minimum 6 characters)');
    } else if (e.code == 'email-already-in-use') {
      print('An account already exists for that email');
    } else {
      print('Error: ${e.message}');
    }
  }
}

4.2 What Firebase Does Automatically

When you call createUserWithEmailAndPassword(), Firebase:

  • Validates email format
  • Checks password strength (minimum 6 characters)
  • Checks for duplicate emails
  • Hashes the password securely
  • Creates user record
  • Returns user information

No backend code needed.

4.3 Common Sign Up Errors

weak-password: Less than 6 characters email-already-in-use: Email already registered invalid-email: Incorrect email format operation-not-allowed: Email/password not enabled in Console


Step 5: User Login (Sign In)

5.1 Sign In Function

Future<void> signIn(String email, String password) async {
  try {
    UserCredential userCredential = await _auth.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
    print("User logged in: ${userCredential.user?.email}");
  } 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');
    } else {
      print('Error: ${e.message}');
    }
  }
}

5.2 Common Sign In Errors

user-not-found: No account with this email wrong-password: Incorrect password invalid-email: Bad email formatuser-disabled: Account disabled by admin


Step 6: Check Current User

Firebase keeps users logged in automatically between app sessions.

User? user = _auth.currentUser;

if (user != null) {
  print("User is logged in: ${user.email}");
  print("User ID: ${user.uid}");
} else {
  print("No user logged in");
}

User object properties:

  • email – User’s email address
  • uid – Unique user ID (use for database queries)
  • emailVerified – Email verification status
  • displayName – User’s display name
  • photoURL – Profile picture URL

Practical Use: Deciding Which Screen to Show

@override
void initState() {
  super.initState();
  
  User? user = _auth.currentUser;
  if (user != null) {
    Navigator.pushReplacementNamed(context, '/home');
  }
}

This is how you implement persistent login.


Step 7: Sign Out User

Future<void> signOut() async {
  try {
    await _auth.signOut();
    print("User signed out");
  } catch (e) {
    print("Error signing out: $e");
  }
}

What happens:

  • Session cleared
  • currentUser becomes null
  • User must provide credentials to sign in again

Step 8: Listen to Authentication State Changes

Firebase provides a real-time stream that notifies you when authentication state changes.

@override
void initState() {
  super.initState();
  
  _auth.authStateChanges().listen((User? user) {
    if (user == null) {
      print('User signed out');
    } else {
      print('User signed in: ${user.email}');
    }
  });
}

When This Fires

The listener triggers when:

  • User signs in
  • User signs out
  • App starts (tells you if someone is already logged in)
  • User’s token is refreshed

Practical Example: Auto-Navigation

class AuthWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
          User? user = snapshot.data;
          
          if (user == null) {
            return LoginScreen();
          } else {
            return HomeScreen();
          }
        }
        
        return Scaffold(
          body: Center(child: CircularProgressIndicator()),
        );
      },
    );
  }
}

This pattern automatically shows the correct screen based on authentication state. I use this in every Flutter practice project that needs authentication.


Complete Example: Authentication Screen

Here’s a complete working example:

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

class AuthScreen extends StatefulWidget {
  @override
  _AuthScreenState createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final TextEditingController emailController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();
  
  bool isLogin = true;
  String errorMessage = '';
  
  Future<void> handleAuth() async {
    setState(() => errorMessage = '');
    
    try {
      if (isLogin) {
        await _auth.signInWithEmailAndPassword(
          email: emailController.text.trim(),
          password: passwordController.text,
        );
      } else {
        await _auth.createUserWithEmailAndPassword(
          email: emailController.text.trim(),
          password: passwordController.text,
        );
      }
    } on FirebaseAuthException catch (e) {
      setState(() => errorMessage = e.message ?? 'An error occurred');
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(isLogin ? 'Sign In' : 'Sign Up'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: emailController,
              decoration: InputDecoration(
                labelText: 'Email',
                border: OutlineInputBorder(),
              ),
              keyboardType: TextInputType.emailAddress,
            ),
            SizedBox(height: 16),
            TextField(
              controller: passwordController,
              decoration: InputDecoration(
                labelText: 'Password',
                border: OutlineInputBorder(),
              ),
              obscureText: true,
            ),
            SizedBox(height: 8),
            if (errorMessage.isNotEmpty)
              Text(errorMessage, style: TextStyle(color: Colors.red)),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: handleAuth,
              child: Text(isLogin ? 'Sign In' : 'Sign Up'),
              style: ElevatedButton.styleFrom(
                minimumSize: Size(double.infinity, 50),
              ),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  isLogin = !isLogin;
                  errorMessage = '';
                });
              },
              child: Text(
                isLogin ? 'Need an account? Sign Up' : 'Have an account? Sign In',
              ),
            ),
          ],
        ),
      ),
    );
  }
  
  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }
}
auth screen showing email/password fields and sign in button

This complete example includes email/password input, toggle between sign in and sign up, error message display, and proper controller disposal.


Common Errors and Solutions

Error: “No Firebase App ‘[DEFAULT]’ has been created”

Cause: Firebase not initialized

Solution: Ensure Firebase.initializeApp() is called in main() before any Firebase code. Review our Firebase Setup Guide if needed.

Error: “The email address is badly formatted”

Cause: Invalid email format

Solution: Add validation:

bool isValidEmail(String email) {
  return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}

Error: “The password must be 6 characters long or more”

Solution: Validate before calling Firebase:

if (password.length < 6) {
  print('Password must be at least 6 characters');
  return;
}

Error: “We have blocked all requests from this device”

Cause: Too many failed attempts

Solution: Wait a few hours, or implement rate limiting


Best Practices

Security:

  • Never store passwords in your app
  • Use email verification for new accounts
  • Validate input on client side first
  • Show user-friendly error messages

Code Organization:

  • Create a separate authentication service class
  • Use StreamBuilder for auth state
  • Implement proper loading states
  • Always dispose controllers

User Experience:

  • Show clear error messages
  • Implement password reset functionality
  • Provide immediate feedback
  • Handle edge cases gracefully

Password Reset Example

Future<void> resetPassword(String email) async {
  try {
    await _auth.sendPasswordResetEmail(email: email);
    print('Password reset email sent');
  } catch (e) {
    print('Error: $e');
  }
}

What You Can Build Next

Once you have authentication working:

User-Specific Apps: To-do lists, note-taking apps, expense trackers

Social Features: User profiles, following systems, comments

E-commerce: Shopping carts, order history, wishlists

Access Control: Admin panels, paid features, role-based access

Firebase Authentication is the foundation for all user-specific functionality. Once users can sign in, you can start building features using their unique user ID.

For storing user data, check out our Cloud Firestore Basics tutorial.


Next Steps

Expand your authentication system:

Email Verification: Confirm users own their email addresses

Password Reset: Let users recover forgotten passwords

Google Sign-In: Add social login options

Profile Updates: Allow display name and photo changes

Start with email/password (what we covered), then add these features as needed.


Related Deadloq Tutorials

Firebase Series:

Flutter Fundamentals:

Projects:


Final Thoughts

Firebase Authentication removes one of the hardest parts of app development. With just a few lines of code, you get a secure, scalable login system that would take weeks to build from scratch.

When I first tried building authentication manually, I spent two weeks on password hashing and session management without even getting password reset working. When I switched to Firebase Authentication, I had a complete system—registration, login, logout, persistent sessions—working in an afternoon.

For Flutter developers learning to build real-world apps, Firebase Authentication is a core skill. It’s mentioned in nearly every Flutter job posting and used in countless production apps.

The code examples in this tutorial form the foundation of production authentication systems. As you build more projects, you’ll add error handling, loading states, and better UX—but the core concepts remain the same.

Authentication is no longer the scary, complex feature it once was. With Firebase and Flutter, it’s become one of the easier parts of app development.


Quick Reference

Enable in Console: Authentication → Sign-in method → Enable Email/Password

Add Package: firebase_auth: ^latest (check pub.dev)

Import: import 'package:firebase_auth/firebase_auth.dart';

Sign Up: await _auth.createUserWithEmailAndPassword(email: email, password: password);

Sign In: await _auth.signInWithEmailAndPassword(email: email, password: password);

Current User: User? user = _auth.currentUser;

Sign Out: await _auth.signOut();

Auth State: _auth.authStateChanges().listen((User? user) { });

Leave a Reply

Your email address will not be published. Required fields are marked *