Autenticazione con Firebase Authentication in Flutter: login email e password
GuideIntermedio40 min Flutter 3.x

Autenticazione con Firebase Authentication in Flutter: login email e password

Autenticazione con Firebase Authentication

Quasi ogni applicazione moderna richiede un sistema di autenticazione. Firebase Authentication offre una soluzione robusta, sicura e pronta all'uso che gestisce per noi la parte più complessa: hashing delle password, gestione delle sessioni e sicurezza.

In questo tutorial costruiremo un flusso completo di autenticazione con email e password: registrazione, login, logout e gestione reattiva dello stato dell'utente tramite uno StreamBuilder.

Prerequisiti: un progetto Flutter funzionante, un account Google e una conoscenza di base dei widget Flutter.

  1. 1

    Creare il progetto Firebase e collegarlo a Flutter

    Per prima cosa creiamo un progetto sulla Firebase Console. Una volta creato, abilitiamo il metodo di accesso Email/Password dalla sezione Authentication > Sign-in method.

    Il modo più semplice e moderno per collegare Firebase a Flutter è usare la FlutterFire CLI. Installiamo gli strumenti necessari ed eseguiamo la configurazione automatica dalla cartella del progetto.

    La CLI genererà automaticamente il file firebase_options.dart con tutte le chiavi di configurazione per le varie piattaforme.

    # Installa la CLI di Firebase (se non presente)
    npm install -g firebase-tools
    firebase login
    
    # Installa la FlutterFire CLI
    dart pub global activate flutterfire_cli
    
    # Dalla cartella del progetto, configura Firebase
    flutterfire configure

    Risultato atteso

    Nella cartella lib/ viene generato il file firebase_options.dart e Firebase è abilitato per le piattaforme selezionate.

  2. 2

    Aggiungere le dipendenze

    Aggiungiamo i pacchetti necessari al file pubspec.yaml: firebase_core per inizializzare Firebase e firebase_auth per l'autenticazione.

    Possiamo usare il comando flutter pub add che inserisce automaticamente le versioni più recenti compatibili.

    flutter pub add firebase_core firebase_auth

    Risultato atteso

    Le dipendenze firebase_core e firebase_auth compaiono nel pubspec.yaml e vengono scaricate.

  3. 3

    Inizializzare Firebase nel main

    Prima di usare qualsiasi servizio Firebase dobbiamo inizializzarlo. Modifichiamo la funzione main rendendola asincrona e chiamando Firebase.initializeApp con le opzioni generate.

    È fondamentale chiamare WidgetsFlutterBinding.ensureInitialized() prima dell'inizializzazione, perché stiamo eseguendo codice asincrono prima di runApp.

    import 'package:flutter/material.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'firebase_options.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Auth Demo',
          theme: ThemeData(useMaterial3: true),
          home: const AuthGate(),
        );
      }
    }

    Risultato atteso

    L'app si avvia senza errori e Firebase risulta correttamente inizializzato.

  4. 4

    Creare un servizio di autenticazione

    Per mantenere il codice pulito, isoliamo la logica di Firebase in una classe AuthService. Questo ci permette di centralizzare i metodi di registrazione, login e logout e di gestire gli errori in modo uniforme.

    Usiamo FirebaseAuth.instance per accedere alle funzionalità e intercettiamo le FirebaseAuthException per restituire messaggi comprensibili all'utente.

    import 'package:firebase_auth/firebase_auth.dart';
    
    class AuthService {
      final FirebaseAuth _auth = FirebaseAuth.instance;
    
      // Stream che notifica i cambiamenti di stato dell'utente
      Stream<User?> get authStateChanges => _auth.authStateChanges();
    
      Future<String?> signUp(String email, String password) async {
        try {
          await _auth.createUserWithEmailAndPassword(
            email: email,
            password: password,
          );
          return null; // nessun errore
        } on FirebaseAuthException catch (e) {
          if (e.code == 'email-already-in-use') {
            return 'Email già registrata.';
          } else if (e.code == 'weak-password') {
            return 'La password è troppo debole.';
          }
          return e.message ?? 'Errore sconosciuto.';
        }
      }
    
      Future<String?> signIn(String email, String password) async {
        try {
          await _auth.signInWithEmailAndPassword(
            email: email,
            password: password,
          );
          return null;
        } on FirebaseAuthException catch (e) {
          if (e.code == 'user-not-found' || e.code == 'wrong-password' || e.code == 'invalid-credential') {
            return 'Credenziali non valide.';
          }
          return e.message ?? 'Errore sconosciuto.';
        }
      }
    
      Future<void> signOut() => _auth.signOut();
    }

    Risultato atteso

    La classe AuthService è pronta e centralizza tutta la logica di autenticazione.

  5. 5

    Gestire lo stato con AuthGate e StreamBuilder

    Il modo reattivo per mostrare la schermata giusta è ascoltare lo stream authStateChanges. Creiamo un widget AuthGate che, tramite uno StreamBuilder, mostra la HomeScreen se l'utente è loggato o la LoginScreen in caso contrario.

    In questo modo l'app reagisce automaticamente a login e logout senza navigazione manuale.

    import 'package:flutter/material.dart';
    import 'package:firebase_auth/firebase_auth.dart';
    
    class AuthGate extends StatelessWidget {
      const AuthGate({super.key});
    
      @override
      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 HomeScreen();
            }
            return const LoginScreen();
          },
        );
      }
    }
    
    class HomeScreen extends StatelessWidget {
      const HomeScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        final user = FirebaseAuth.instance.currentUser;
        return Scaffold(
          appBar: AppBar(
            title: const Text('Home'),
            actions: [
              IconButton(
                icon: const Icon(Icons.logout),
                onPressed: () => AuthService().signOut(),
              ),
            ],
          ),
          body: Center(child: Text('Benvenuto, ${user?.email ?? ''}')),
        );
      }
    }

    Risultato atteso

    All'avvio l'app mostra la LoginScreen; dopo il login passa automaticamente alla HomeScreen.

  6. 6

    Costruire la schermata di login e registrazione

    Creiamo una LoginScreen con due campi (email e password) e un pulsante che alterna tra modalità login e registrazione. Validiamo i campi e mostriamo eventuali errori restituiti da AuthService tramite uno SnackBar.

    Usiamo uno StatefulWidget per gestire lo stato dei controller e l'indicatore di caricamento.

    class LoginScreen extends StatefulWidget {
      const LoginScreen({super.key});
    
      @override
      State<LoginScreen> createState() => _LoginScreenState();
    }
    
    class _LoginScreenState extends State<LoginScreen> {
      final _emailCtrl = TextEditingController();
      final _passCtrl = TextEditingController();
      final _auth = AuthService();
      bool _isLogin = true;
      bool _loading = false;
    
      Future<void> _submit() async {
        setState(() => _loading = true);
        final error = _isLogin
            ? await _auth.signIn(_emailCtrl.text.trim(), _passCtrl.text.trim())
            : await _auth.signUp(_emailCtrl.text.trim(), _passCtrl.text.trim());
        setState(() => _loading = false);
        if (error != null && mounted) {
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: Text(error)));
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(_isLogin ? 'Login' : 'Registrazione')),
          body: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextField(
                  controller: _emailCtrl,
                  decoration: const InputDecoration(labelText: 'Email'),
                  keyboardType: TextInputType.emailAddress,
                ),
                const SizedBox(height: 12),
                TextField(
                  controller: _passCtrl,
                  decoration: const InputDecoration(labelText: 'Password'),
                  obscureText: true,
                ),
                const SizedBox(height: 24),
                _loading
                    ? const CircularProgressIndicator()
                    : ElevatedButton(
                        onPressed: _submit,
                        child: Text(_isLogin ? 'Accedi' : 'Registrati'),
                      ),
                TextButton(
                  onPressed: () => setState(() => _isLogin = !_isLogin),
                  child: Text(_isLogin
                      ? 'Non hai un account? Registrati'
                      : 'Hai già un account? Accedi'),
                ),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    L'utente può registrarsi, accedere e fare logout; lo stato si aggiorna automaticamente grazie all'AuthGate.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!