Skip to main content

Firebase Authentication

About 3 min

Firebase Authentication

Most real apps need to know who is using the app. In Travel Notes, authentication is necessary because trips are personal. A user should only see their own travel plans.

Firebase Authentication gives us a ready-made authentication system, so we do not have to store passwords ourselves.

Before you start

Make sure you created the travel_notes_app, configured Firebase with flutterfire configure and initialized Firebase in the previous chapter.

In this chapter we will use email and password authentication. Firebase also supports other providers, such as Google sign-in, Apple sign-in and phone authentication, but email and password is the clearest way to learn the flow.

Enable email/password login

  • Open the Firebase consoleopen in new window.
  • Open your Firebase project.
  • Go to Security > Authentication.
  • Click Get started if Authentication is not enabled yet.
  • Open the Sign-in method tab.
  • Enable Email/Password.
  • Save the changes.

Check the package

In the previous chapter we already added firebase_auth:

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^4.9.0
  cloud_firestore: ^6.4.1
  firebase_auth: ^6.5.1





 

If you skipped that step, install it now:

flutter pub add firebase_auth

Auth service

Create a new folder services in the lib folder. In this folder, create a file auth_service.dart.

import 'package:firebase_auth/firebase_auth.dart';

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Stream<User?> get authStateChanges {
    return _auth.authStateChanges();
  }

  User? get currentUser {
    return _auth.currentUser;
  }

  Future<void> signIn({
    required String email,
    required String password,
  }) async {
    await _auth.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
  }

  Future<void> register({
    required String email,
    required String password,
  }) async {
    await _auth.createUserWithEmailAndPassword(
      email: email,
      password: password,
    );
  }

  Future<void> signOut() async {
    await _auth.signOut();
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
codedescription
FirebaseAuth.instancegives access to Firebase Authentication
authStateChanges()tells the app when the user logs in or logs out
currentUserreturns the currently logged-in user, or null
signInWithEmailAndPasswordlogs in an existing user
createUserWithEmailAndPasswordcreates a new user
signOutlogs out the current user

Auth gate

An auth gate decides which page the user should see:

  • if there is no user: show the login page
  • if there is a user: show the protected app

Create auth_gate.dart in the pages folder.

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

import '../services/auth_service.dart';
import 'login.dart';
import 'trip_list.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});

  static final AuthService authService = AuthService();

  
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: authService.authStateChanges,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        }

        if (snapshot.hasData) {
          return const TripListPage();
        }

        return const LoginPage();
      },
    );
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Temporary trip list page

Create trip_list.dart in the pages folder. For now, this page only proves that login works. In the next chapter we will replace it with a realtime Firestore list.

import 'package:flutter/material.dart';

import '../services/auth_service.dart';

class TripListPage extends StatelessWidget {
  const TripListPage({super.key});

  static final AuthService authService = AuthService();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("My trips"),
        actions: [
          IconButton(
            onPressed: authService.signOut,
            icon: const Icon(Icons.logout),
          ),
        ],
      ),
      body: const Center(
        child: Text("You are logged in"),
      ),
    );
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Login page

Create login.dart in the pages folder.

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

import '../services/auth_service.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final AuthService authService = AuthService();
  final TextEditingController emailController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();

  bool isRegistering = false;
  bool isLoading = false;
  String? errorMessage;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(isRegistering ? "Create account" : "Login"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: "Email",
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 12),
            TextField(
              controller: passwordController,
              obscureText: true,
              decoration: const InputDecoration(
                labelText: "Password",
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 12),
            if (errorMessage != null)
              Text(
                errorMessage!,
                style: const TextStyle(color: Colors.red),
              ),
            const SizedBox(height: 12),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: isLoading ? null : _submit,
                child: isLoading
                    ? const CircularProgressIndicator()
                    : Text(isRegistering ? "Create account" : "Login"),
              ),
            ),
            TextButton(
              onPressed: isLoading
                  ? null
                  : () {
                      setState(() {
                        isRegistering = !isRegistering;
                        errorMessage = null;
                      });
                    },
              child: Text(
                isRegistering
                    ? "Already have an account? Login"
                    : "No account yet? Create one",
              ),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _submit() async {
    setState(() {
      isLoading = true;
      errorMessage = null;
    });

    try {
      if (isRegistering) {
        await authService.register(
          email: emailController.text.trim(),
          password: passwordController.text,
        );
      } else {
        await authService.signIn(
          email: emailController.text.trim(),
          password: passwordController.text,
        );
      }
    } on FirebaseAuthException catch (error) {
      setState(() {
        errorMessage = error.message;
      });
    } finally {
      if (mounted) {
        setState(() {
          isLoading = false;
        });
      }
    }
  }

  
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Use the auth gate

In main.dart, replace home: const SetupPage() with home: const AuthGate() and import the auth gate.

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

import 'firebase_options.dart';
import 'pages/auth_gate.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(const TravelNotesApp());
}

class TravelNotesApp extends StatelessWidget {
  const TravelNotesApp({super.key});

  
  Widget build(BuildContext context) {
    final ColorScheme colors = ColorScheme.fromSeed(
      seedColor: Colors.teal,
    );

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Travel Notes',
      theme: ThemeData(
        colorScheme: colors,
        appBarTheme: AppBarTheme(
          backgroundColor: colors.primary,
          foregroundColor: colors.onPrimary,
        ),
      ),
      home: const AuthGate(),
    );
  }
}




 





























 



Run the app. You should be able to create an account, log out and log in again.

Why authentication matters for Firestore

Authentication gives every user a unique uid. In the next chapter we will use that uid in the Firestore path:

users
  userUid
    trips
      tripId
        title: "Summer in Japan"
        destination: "Tokyo"

This means each user gets their own trips collection. That is one of the reasons Firestore works well for user-centered mobile apps.