Travailler avec Firebase Authentication dans Flutter
Sommaire
- 1- Objectif
- 2- Introduction à Firebase Authentication
- 3- Fonctionnalités principales de l'authentification Firebase
- 4- Méthodes principales d’authentification
- 4.1- Authentification Email / Mot de passe
- 4.2- Authentification via Google Sign-In
- 4.3- Authentification Anonyme
- 4.4- Résumé visuel
- 5- Implémenter l'authentification Firebase
- 6- Connexion Flutter au service Firebase Authentication
- 6.1- Ajouter Firebase à Flutter
- 6.2- Créer un formulaire d'authentification (Email / Mot de passe)
- 6.3- Se connecter avec email et mot de passe dans ton application Flutter
- 6.3.1- Créer un utilisateur directement dans la console Firebase (méthode rapide pour tester)
- 6.3.2- Créer un utilisateur depuis ton application Flutter
- 7- Activation de l’authentification Google dans Flutter
- 7.1- Préparer le projet Flutter
- 7.2- Configuration côté Firebase
- 7.3- Ajouter les clés SHA-1 & SHA-256 (ÉTAPE OBLIGATOIRE)
- 7.3.1- Obtenir SHA-1 et SHA-256
- 7.3.2- Ajouter les empreintes dans Firebase
- 7.4- Ajouter Google Sign-In dans le Controller
- 7.4.1- Import nécessaire
- 7.4.2- Ajouter l’instance Google
- 7.4.3- Méthode signInWithGoogle
- 7.4.4- Méthode
logout()(déconnexion) - 7.5- Ajouter le bouton Google dans la page de Login
- 7.6- Tester - Problèmes courants - Google Sign-In
- 8- Gestion des rôles utilisateurs avec Firebase Authentication et Flutter
- 8.1- Introduction
- 8.2- Structure générale du système d’authentification
- 8.3- Architecture du projet Flutter
- 8.4- Fonctionnement général
- 8.4.1- Lorsqu’un utilisateur s’inscrit
- 8.4.2- Lorsqu’un utilisateur se connecte
- 8.4.3- Modèle UserModel étendu
- 8.4.4- Service Firestore pour gérer les utilisateurs
- 8.4.5- authController modifié
- 8.4.6- Vue d'inscription modifiée
- 8.4.7- Vue Admin pour gérer les approbations
- 8.4.8- Modifications dans main.dart
- 8.4.9- Configuration Firestore
- 8.4.10- contrôleur d’authentification (auth_controller.dart)
- 8.4.11- Gestion de la redirection automatique (main.dart)
- 8.4.12- Règles de sécurité Firestore
- 8.4.13- contrôleur d’authentification (auth_controller.dart)
- 9- Ajout d’un utilisateur et enregistrement simultané dans Firebase Authentication et Cloud Firestore
- 9.1- Objectif pédagogique
- 9.2- Pourquoi deux enregistrements ?
- 9.3- Étapes du processus d’inscription
- 9.3.1- Cours Flutter
Travailler avec Firebase Authentication dans Flutter
-
Objectif
- Comprendre les différents modes d’authentification Firebase
- Implémenter l’authentification email/mot de passe
- Gérer les états d’authentification dans Flutter
- Sécuriser l’accès aux données Firestore
-
Introduction à Firebase Authentication
- Qu’est-ce que Firebase Authentication?
Firebase Authenticationest un service backend puissant et sécurisé fourni par Google qui permet d’authentifier les utilisateurs dans vos applications de manière simple et rapide.Firebase Authenticationest un service backend performant proposé par Google Firebase , conçu pour accélérer l’authentification des utilisateurs dans les applications. Prenant en charge diverses méthodes d’authentification, telles que l’adresse e-mail/mot de passe, le numéro de téléphone et les connexions via les réseaux sociaux , Firebase Authentication garantit une authentification et une gestion des identités sécurisées.- En quoi consiste exactement ce service ?
- Imaginez devoir créer vous-même tout un système de :
- Création de comptes utilisateurs
- Vérification d’emails
- Réinitialisation de mots de passe
- Gestion des sessions
- Sécurité des données…
- Firebase Authentication fait tout ce travail complexe pour vous !
- Code source de la vidéo
-
Fonctionnalités principales de l’authentification Firebase
- Plusieurs fournisseurs d’authentification : Firebase Authentication prend en charge plusieurs méthodes d’authentification, notamment l’adresse e-mail/mot de passe, le numéro de téléphone, la connexion via les réseaux sociaux (Google, Facebook, Twitter, GitHub), et bien plus encore.
- Intégration facile : Firebase Authentication fournit des SDK pour les plateformes populaires, ce qui facilite l’intégration de l’authentification dans vos applications en quelques lignes de code seulement.
- Sécurité : Firebase Authentication gère l’authentification des utilisateurs de manière sécurisée, en utilisant des protocoles de chiffrement et d’authentification conformes aux normes de l’industrie afin de protéger les données et les informations d’identification des utilisateurs.
- Gestion des utilisateurs : Firebase Authentication fournit des outils pour la gestion des comptes utilisateurs, notamment la création d’utilisateurs , la réinitialisation des mots de passe , la vérification des adresses e-mail et la suppression des comptes .
- Personnalisation : Vous pouvez personnaliser l’expérience d’authentification dans votre application, notamment en personnalisant l’ interface utilisateur , en mettant en œuvre l’authentification multifacteurs et en appliquant des politiques de mots de passe.
-
Méthodes principales d’authentification
- Firebase propose 3 méthodes principales d’authentification :
-
Authentification Email / Mot de passe
- Nécessite un email + un mot de passe
- Création d’un compte Firebase classique.
- L’utilisateur doit saisir ses identifiants.
- Exemple :
_auth.createUserWithEmailAndPassword(email: "...", password: "..."); - Avantage : Simple et classique
- Inconvénient: L’utilisateur doit taper un email et un mot de passe.
-
Authentification via Google Sign-In
- Ne nécessite ni mot de passe, ni saisie d’email
- Google fournit automatiquement :l’email,le nom,la photo,un token sécurisé.
- L’utilisateur clique → Choisit son compte Google → Connecté.
- Exemple : GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
- Avantage : Ultra rapide, Pas de mot de passe à mémoriser, Très sécurisé
- Inconvénient: Nécessite une configuration Firebase + SHA-1/SHA-256
- Nécessite un compte Google valide
-
Authentification Anonyme
- Ne nécessite ni email, ni mot de passe, ni Google
- Firebase crée un utilisateur temporaire sans identité.
- Exemple :
FirebaseAuth.instance.signInAnonymously(); - Avantages: Accès immédiat (invité), Parfait pour tester une app, Utile pour les apps où on veut essayer avant de créer un compte
- Inconvénients : L’utilisateur n’a pas de profil, pas de photo, pas d’email, Si l’utilisateur désinstalle l’app → Compte perdu (sauf s’il est lié plus tard à Google/email)
-
Résumé visuel
-
Implémenter l’authentification Firebase
- Nous utilisons le lien https://console.firebase.google.com/ pour nous connecter à Firebase et cliquons sur « Accéder à la console », sélectionnons le projet que nous avons déjà configuré, puis choisissons le produit d’authentification :
- Un message concernant le produit apparaît :
- L’étape suivante consiste à sélectionner les méthodes de connexion que vous allez accepter :
- Pour ce tutoriel, nous allons utiliser trois méthodes d’authentification : « Adresse e-mail / Mot de passe », « Anonyme » et « Compte Google ».
Commencez par cliquer sur « Adresse e-mail / Mot de passe » dans la section « Fournisseurs natifs » afin de l’activer. - Cliquez sur le bouton pour activer l’option « Courriel/Mot de passe » (veuillez ne pas utiliser le « Lien courriel » pour le moment, car cela nécessite des étapes de configuration supplémentaires). Cliquez sur « Enregistrer ».
- Pour le moment, seul le fournisseur « Courriel/Mot de passe » est activé. Cliquez sur « Ajouter un nouveau fournisseur » pour l’étape suivante et choisissez « Anonyme ».
- Cliquez sur le bouton pour activer le mode « Anonyme », puis sur « Enregistrer » pour passer à l’étape suivante. Dans l’aperçu, cliquez sur « Google ».
- Pour les autres fournisseurs, activez Google comme fournisseur d’authentification. Une note concerne l’« Empreinte SHA-1 » ; nous y reviendrons. Vous pouvez modifier le « Nom public » ; je conserve la valeur par défaut. Il faut ensuite fournir une « Adresse e-mail d’assistance » ; utilisez celle par défaut de votre compte Google Firebase. Cliquez enfin sur « Enregistrer ».
-
Connexion Flutter au service Firebase Authentication
- Pour permettre à votre application Flutter d’utiliser Firebase Authentication, vous devez d’abord
ajouter Firebase à votre projet Flutter. Cela implique l’installation des bibliothèques Firebase, la
configuration de Firebase dans Flutter, et l’implémentation du code d’authentification. -
Ajouter Firebase à Flutter
- Dans votre projet Flutter, ouvrez le fichier
pubspec.yamlet
ajoutez les dépendances suivantes : - Exécutez la commande suivante pour installer les dépendances :
- Puis, initialisez Firebase dans votre application. Dans le fichier main.dart, modifiez la
fonction main() comme suit : -
Créer un formulaire d’authentification (Email / Mot de passe)
- Nous allons créer un formulaire simple dans Flutter avec deux champs de texte : un pour l’email
et un pour le mot de passe, ainsi qu’un bouton de connexion. - Ajouter un contrôleur d’authentification
- Créez login_view.dart dans le dossier views :
- Ce code permet à l’utilisateur de se connecter en utilisant son email et son mot de passe
enregistrés sur Firebase. - Modifier
main.dartpour afficher la bonne interface selon la connexion -
Se connecter avec email et mot de passe dans ton application Flutter
- Pour te connecter avec email et mot de passe dans ton application Flutter, tu dois d’abord avoir un
compte utilisateur créé dans Firebase Authentication (base de données d’utilisateurs Firebase).
Voici deux façons de procéder : -
Créer un utilisateur directement dans la console Firebase (méthode rapide pour tester)
- Va dans la console Firebase :
https://console.firebase.google.com - Sélectionne ton projet.
- Dans le menu de gauche, clique sur Authentication.
- Va dans l’onglet Utilisateurs.
- Clique sur le bouton « Ajouter un utilisateur » en haut à droite.
- Renseigne un email (ex: test@example.com) et un mot de passe (ex: 12345678).
- Clique sur Ajouter un utilisateur.
- Tu peux maintenant utiliser ces identifiants dans ton application Flutter via le formulaire
de connexion. -
Créer un utilisateur depuis ton application Flutter
- Tu peux aussi permettre à l’utilisateur de s’inscrire depuis ton app en ajoutant un bouton
ou une page d’inscription (SignUp) avec :

models/user_model.dart
/// Modèle représentant un utilisateur dans l'application.
/// Ce modèle sert à extraire uniquement les informations utiles
/// de l'objet User retourné par Firebase Authentication.
class UserModel {
/// Identifiant unique de l'utilisateur (UID) fourni par Firebase.
final String uid;
/// Email de l'utilisateur.
/// Peut être vide si Firebase ne fournit pas d'email (ex: connexion anonyme).
final String email;
/// Constructeur de la classe UserModel.
/// Les deux champs sont requis.
UserModel({required this.uid, required this.email});
/// Factory permettant de créer un UserModel à partir d'un objet
/// Firebase User (FirebaseAuth.instance.currentUser).
/// Cela facilite la conversion des données Firebase vers ton modèle local.
factory UserModel.fromFirebaseUser(user) {
return UserModel(
uid: user.uid, // Récupère l'UID de l'utilisateur Firebase
email: user.email ?? "", // Récupère l'email ou une chaîne vide si null
);
}
}
controllers/auth_controller.dart
import 'package:firebase_auth/firebase_auth.dart';
/// Contrôleur responsable de la gestion de l'authentification
/// via Firebase Authentication.
///
/// Ce controller regroupe toutes les opérations :
/* connexion
- inscription
- déconnexion
- gestion des erreurs*/
class AuthController {
/// Instance principale de FirebaseAuth utilisée pour toutes
/// les opérations d'authentification.
final FirebaseAuth _auth = FirebaseAuth.instance;
// ------------------------------------------------------------------
// Connexion Email / Mot de passe
// ------------------------------------------------------------------
/// Tente de connecter un utilisateur via email et mot de passe.
///
/// Retourne :
/// • null → si la connexion réussit
/// • String → si une erreur doit être affichée dans l'UI
Future<String?> login({
required String email,
required String password,
}) async {
try {
// Nettoie les champs (supprime espaces au début/fin)
await _auth.signInWithEmailAndPassword(
email: email.trim(),
password: password.trim(),
);
return null; // aucune erreur → succès
} on FirebaseAuthException catch (e) {
// Conversion du code d'erreur Firebase en message lisible
return getErrorMessage(e);
}
}
// ------------------------------------------------------------------
// Inscription Email / Mot de passe
// ------------------------------------------------------------------
/// Crée un nouveau compte utilisateur avec email et mot de passe.
///
/// Retourne :
/// • null → si l'inscription réussit
/// • String → si une erreur doit être affichée
Future<String?> register({
required String email,
required String password,
}) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email.trim(),
password: password.trim(),
);
return null; // succès
} on FirebaseAuthException catch (e) {
return getErrorMessage(e); // message d'erreur lisible
}
}
// ------------------------------------------------------------------
// Déconnexion
// ------------------------------------------------------------------
/// Déconnecte l'utilisateur actuellement connecté.
Future<void> logout() async {
await _auth.signOut();
}
// ------------------------------------------------------------------
// Vérifier l'utilisateur connecté
// ------------------------------------------------------------------
/// Renvoie l'utilisateur actuellement connecté, ou null
/// s'il n'y a personne.
User? get currentUser => _auth.currentUser;
/// Émet un flux indiquant en temps réel si un utilisateur
/// se connecte / se déconnecte.
///
/// Très utile pour gérer la navigation automatique.
Stream<User?> authStateChanges() => _auth.authStateChanges();
// ------------------------------------------------------------------
// Gestion des erreurs Firebase
// ------------------------------------------------------------------
/// Convertit un code d'erreur Firebase en message plus lisible
/// pour l'utilisateur final.
///
/// Exemple : "user-not-found" → "Aucun utilisateur trouvé avec cet email."
String getErrorMessage(FirebaseAuthException e) {
switch (e.code) {
case "user-not-found":
return "Aucun utilisateur trouvé avec cet email.";
case "wrong-password":
return "Mot de passe incorrect.";
case "invalid-email":
return "L'email n'est pas valide.";
case "email-already-in-use":
return "Cet email est déjà utilisé.";
case "weak-password":
return "Le mot de passe est trop faible.";
case "user-disabled":
return "Ce compte est désactivé.";
default:
return "Erreur : ${e.message}";
}
}
}
views/login_view.dart
import 'package:authentification_demo/views/signup_view.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class LoginView extends StatefulWidget {
const LoginView({super.key});
@override
State<LoginView> createState() => _LoginViewState();
}
class _LoginViewState extends State<LoginView> {
final emailController = TextEditingController();
final passwordController = TextEditingController();
bool isLoading = false;
String? errorMessage;
Future<void> login() async {
setState(() {
isLoading = true;
errorMessage = null;
});
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text.trim(),
password: passwordController.text.trim(),
);
} on FirebaseAuthException catch (e) {
setState(() {
errorMessage = e.message;
});
} finally {
setState(() {
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF6A11CB), Color(0xFF2575FC)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Center(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(24),
margin: const EdgeInsets.symmetric(horizontal: 25),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 12,
offset: const Offset(0, 6),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.lock_outline,
size: 70,
color: Color(0xFF6A11CB),
),
const SizedBox(height: 15),
const Text(
"Bienvenue",
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Color(0xFF6A11CB),
),
),
const SizedBox(height: 25),
// -------- Email ----------
TextField(
controller: emailController,
decoration: InputDecoration(
labelText: "Email",
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
const SizedBox(height: 15),
// -------- Password ----------
TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: "Mot de passe",
prefixIcon: const Icon(Icons.lock_outline),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
const SizedBox(height: 15),
// -------- Error Message ----------
if (errorMessage != null)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.redAccent.withOpacity(0.2),
borderRadius: BorderRadius.circular(10),
),
child: Text(
errorMessage!,
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 20),
// -------- Login Button ----------
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isLoading ? null : login,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: const Color(0xFF6A11CB),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"Se connecter",
style: TextStyle(
fontSize: 18,
color: Colors.white,
),
),
),
),
const SizedBox(height: 10),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SignUpView()),
);
},
child: const Text(
"Créer un compte",
style: TextStyle(color: Colors.black54),
),
),
],
),
),
),
),
),
);
}
}
views/signup_view.dart
import 'package:flutter/material.dart';
import '../controllers/auth_controller.dart';
class SignUpView extends StatefulWidget {
const SignUpView({super.key});
@override
State<SignUpView> createState() => _SignUpViewState();
}
class _SignUpViewState extends State<SignUpView> {
final auth = AuthController();
final emailController = TextEditingController();
final passwordController = TextEditingController();
String? errorMessage;
bool isLoading = false;
Future<void> register() async {
setState(() { isLoading = true; });
final error = await auth.register(
email: emailController.text,
password: passwordController.text,
);
if (error != null) {
setState(() => errorMessage = error);
}
setState(() => isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.symmetric(horizontal: 25),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF11998E), Color(0xFF38EF7D)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Center(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
const Text(
"Créer un compte",
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Color(0xFF11998E),
),
),
const SizedBox(height: 25),
TextField(
controller: emailController,
decoration: InputDecoration(
labelText: "Email",
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
const SizedBox(height: 15),
TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: "Mot de passe",
prefixIcon: const Icon(Icons.lock_outline),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
const SizedBox(height: 20),
if (errorMessage != null)
Text(
errorMessage!,
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isLoading ? null : register,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
backgroundColor: const Color(0xFF11998E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"S'inscrire",
style: TextStyle(fontSize: 18),
),
),
),
const SizedBox(height: 10),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Déjà un compte ? Se connecter"),
),
],
),
),
),
),
),
);
}
}
views/home_view.dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class HomeView extends StatelessWidget {
const HomeView({super.key});
@override
Widget build(BuildContext context) {
final user = FirebaseAuth.instance.currentUser;
return Scaffold(
appBar: AppBar(
title: const Text("Accueil"),
backgroundColor: Colors.deepPurple,
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await FirebaseAuth.instance.signOut();
// Redirection vers la page de login
Navigator.of(context).pushReplacementNamed('/login');
},
),
],
),
body: Center(
child: Text(
"Bienvenue ${user?.email ?? 'Utilisateur'}",
style: const TextStyle(fontSize: 22),
),
),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'views/login_view.dart';
import 'views/home_view.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return const HomeView();
}
return const LoginView();
},
),
);
}
}
firebase_options.dart
// File: lib/firebase_options.dart
import 'package:firebase_core/firebase_core.dart';
/*
Remplacer les valeurs par celles de ton google-services.json :
current_key → apiKey
mobilesdk_app_id → appId
project_id → projectId
storage_bucket → storageBucket
project_number → messagingSenderId*/
class DefaultFirebaseOptions {
static const FirebaseOptions android = FirebaseOptions(
apiKey: '...................',
appId: '......................',
messagingSenderId: '.............',
projectId: '.................',
storageBucket: '...................',
);
static FirebaseOptions get currentPlatform {
return android;
}
}
| Méthode | Email ? | Mot de passe ? | Pop-up Google ? | Identité garantie ? |
|---|---|---|---|---|
| Email/Password | ✔ oui | ✔ oui | ✖ non | ✔ oui |
| Google Sign-In | ✔ récupéré automatiquement | ✖ non | ✔ oui | ✔ oui |
| Anonyme | ✖ non | ✖ non | ✖ non | non |





dependencies:
flutter:
sdk: flutter
firebase_core: ^3.0.0
firebase_auth: ^5.0.0
google_sign_in: ^7.0.0
flutter pub get
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
auth_controller.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthController with ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
User? get currentUser => _auth.currentUser;
// Connexion avec email et mot de passe
Future<String?> login(String email, String password) async {
try {
await _auth.signInWithEmailAndPassword(email: email, password: password);
return null; // Succès
} on FirebaseAuthException catch (e) {
return e.message; // Retourne l'erreur
}
}
// Déconnexion
Future<void> logout() async {
await _auth.signOut();
}
// Écouteur d'état
Stream<User?> authStateChanges() {
return _auth.authStateChanges();
}
}
login_view.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../controllers/auth_controller.dart';
class LoginView extends StatelessWidget {
final TextEditingController emailCtrl = TextEditingController();
final TextEditingController passCtrl = TextEditingController();
@override
Widget build(BuildContext context) {
final authController = Provider.of<AuthController>(context);
return Scaffold(
appBar: AppBar(title: const Text("Connexion")),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: emailCtrl,
decoration: InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 15),
TextField(
controller: passCtrl,
decoration: InputDecoration(labelText: 'Mot de passe'),
obscureText: true,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
final res = await authController.login(
emailCtrl.text.trim(),
passCtrl.text.trim(),
);
if (res == null) {
// Succès – redirection automatique dans le StreamBuilder (voir étape 3)
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(res)),
);
}
},
child: const Text('Se connecter'),
)
],
),
),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:provider/provider.dart';
import 'firebase_options.dart';
import 'controllers/livre_controller.dart';
import 'controllers/auth_controller.dart';
import 'views/liste_livres_view_recherche.dart';
import 'views/login_view.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<LivreController>(
create: (_) => LivreController(),
),
ChangeNotifierProvider<AuthController>(
create: (_) => AuthController(),
),
],
child: MaterialApp(
title: 'Ma Bibliothèque',
theme: ThemeData(
useMaterial3: false,
primaryColor: const Color(0xFF4CAF50),
),
debugShowCheckedModeBanner: false,
home: AuthWrapper(), // Changement important
),
);
}
}
class AuthWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
final authController = Provider.of<AuthController>(context, listen: false);
return StreamBuilder(
stream: authController.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()));
}
if (snapshot.hasData) {
return const ListeLivresView(); // Authentifié
}
return LoginView(); // Non connecté
},
);
}
}
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: emailInput,
password: passwordInput,
);
Activation de l’authentification Google dans Flutter
-
Préparer le projet Flutter
- Avant de commencer, il faut ajouter les packages nécessaires.
- Ajouter les dépendances dans pubspec.yaml
- Puis exécuter :
flutter pub get -
Configuration côté Firebase
- Activer
Google Sign-indans Firebase - Aller à : Firebase Console → Authentication → Sign-in methods
- Activer Google
- Cliquer sur Enable
- Enregistrer.
-
Ajouter les clés SHA-1 & SHA-256 (ÉTAPE OBLIGATOIRE)
- Sans cette étape, tu obtiens l’erreur : PlatformException(sign_in_failed, ApiException:10)
- Pourquoi ? Parce que Google Sign-In mobile nécessite que Firebase reconnaisse ton application via ses empreintes SHA.
-
Obtenir SHA-1 et SHA-256
- Méthode 1 – Avec Flutter (la meilleure)
- Dans le terminal, va dans le dossier android du projet :
cd android - Puis exécute :
./gradlew signingReport - Tu obtiendras un tableau contenant :
- SHA1: F1:23:A4:…
- SHA256: 3A:1F:90:…
- Méthode 2 – Avec Android Studio
- Ouvrir Android Studio
- Ouvrir le module Android :
android/→ clic droit → Open in Android Studio - Aller dans : Gradle → Tasks → android → signingReport
- Lire les empreintes SHA-1 et SHA-256
- Méthode 3 – Avec Gradle (Flutter / Android) – Recommandée
- C’est la méthode la plus fiable. Elle génère les clés pour toutes les variantes (debug / release).
- Ouvre un terminal dans le dossier racine du projet Flutter
- Exécute :
- Windows :
cd android .\gradlew signingReport - macOS / Linux :
cd android ./gradlew signingReport - Copie les valeurs SHA1 et SHA-256 affichées sous la variante debug (et release si tu comptes publier)
-
Ajouter les empreintes dans Firebase
- Ouvre la Firebase Console → ton projet.
- Clique sur Paramètres du projet (icône roue dentée) → Paramètres du projet.
- Sous Tes apps, sélectionne ton application Android (ou ajoute-la si nécessaire).
- Dans Empreintes SHA (SHA certificates / SHA fingerprints), clique Ajouter une empreinte.
- Colle SHA-1, clique enregistrer.
- Répète pour SHA-256.
- Enregistrer
- Télécharger le fichier google-services.json mis à jour
- Remplacer celui dans :
android/app/google-services.json -
Ajouter Google Sign-In dans le Controller
-
Import nécessaire
-
Ajouter l’instance Google
-
Méthode signInWithGoogle
-
Méthode
logout()(déconnexion) -
Ajouter le bouton Google dans la page de Login
-
Tester – Problèmes courants – Google Sign-In
dependencies:
firebase_core: ^4.2.1
firebase_auth: ^6.1.2
google_sign_in: ^6.1.5

import 'package:google_sign_in/google_sign_in.dart';
final GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: ['email', 'profile'],
signInOption: SignInOption.standard,
);
// ------------------------------------------------------------------
// MÉTHODE POUR GOOGLE : Connexion via Google Sign-In + Firebase
// ------------------------------------------------------------------
Future<String?> signInWithGoogle() async {
try {
// 1️- Ouvre la fenêtre de connexion Google
// Si l'utilisateur annule (ferme la pop-up), googleUser sera null.
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) return "Connexion annulée";
// 2️- Récupère les informations d’authentification Google :
// - accessToken
// - idToken
// Ces tokens serviront à authentifier l’utilisateur auprès de Firebase.
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// 3️- Crée les credentials Firebase à partir des tokens Google
// Firebase utilise ces identifiants pour vérifier la connexion.
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken, // jeton d’accès
idToken: googleAuth.idToken, // jeton d'identité
);
// 4️- Connexion à Firebase avec les identifiants Google
// Si tout est correct, l’utilisateur sera connecté immédiatement.
await _auth.signInWithCredential(credential);
return null; // Connexion réussie
} on FirebaseAuthException catch (e) {
// 5️- Gestion des erreurs Firebase
// Utilise votre fonction existante pour générer un message propre.
return getErrorMessage(e);
} catch (e) {
// 6️- Gestion des autres erreurs (Google Sign-In, réseau, etc.)
return "Erreur Google Sign-In : $e";
}
}
// ------------------------------------------------------------------
// Déconnexion
// ------------------------------------------------------------------
/// Déconnecte l'utilisateur actuellement connecté.
Future<void> logout() async {
await _googleSignIn.signOut(); // ← AJOUTER CETTE LIGNE POUR GOOGLE
await _auth.signOut();
}
ElevatedButton.icon(
onPressed: isLoadingGoogle ? null : loginWithGoogle,
icon: Icon(Icons.login, color: Colors.red),
label: isLoadingGoogle
? CircularProgressIndicator(strokeWidth: 2)
: Text("Continuer avec Google"),
)
| Problème | Cause | Solution |
|---|---|---|
| ApiException:10 | ✖ SHA-1 / SHA-256 manquantes | Ajouter les empreintes (SHA-1 & SHA-256) dans la console Firebase |
| Popup Google en anglais | Google Sign-In force la langue selon l’appareil | Impossible de forcer directement – laisser la langue système de l’appareil |
| Crash ou écran blanc | google-services.json non mis à jour | Télécharger la nouvelle version de google-services.json depuis Firebase et remplacer celle du projet |
| App non reconnue | Mauvais package name | Vérifier et corriger le package dans AndroidManifest.xml et dans la console Firebase |
Gestion des rôles utilisateurs avec Firebase Authentication et Flutter
- Apprendre à combiner l’authentification Firebase et Firestore pour gérer différents rôles (admin, auteur, lecteur) dans une application Flutter.
-
Introduction
- Firebase Authentication permet de gérer l’inscription et la connexion des utilisateurs.
- Cependant, elle ne gère que l’identité (email, mot de passe, etc.).
- Pour attribuer un rôle (ex. admin, auteur, lecteur), nous devons stocker des informations supplémentaires dans Cloud Firestore.
- C’est donc la combinaison de Firebase Auth + Firestore qui permet une gestion complète des rôles et des autorisations.
-
Structure générale du système d’authentification
-
Architecture du projet Flutter
-
Fonctionnement général
-
Lorsqu’un utilisateur s’inscrit
- Son compte est créé dans Firebase Auth (createUserWithEmailAndPassword).
- Son profil est ajouté dans Firestore avec :
- Il est déconnecté automatiquement.
- L’admin doit valider son compte avant qu’il puisse se connecter.
| Composant | Rôle |
|---|---|
| Firebase Authentication | Gère les identifiants (email, mot de passe, UID) |
| Cloud Firestore | Stocke les rôles et le statut de validation des utilisateurs |
| AuthController | Gère la logique d’inscription, de connexion, et de validation |
| AuthWrapper | Oriente l’utilisateur vers la bonne interface (admin, auteur, lecteur) |
lib/
├── controllers/
│ └── auth_controller.dart ← logique d’authentification
├── views/
│ ├── login_view.dart ← page de connexion
│ ├── signup_view.dart ← page d’inscription
│ ├── admin_dashboard_view.dart ← interface de l’admin
│ ├── liste_livres_view.dart ← interface des auteurs/lecteurs
│ └── pending_view.dart ← affichée si compte non validé
└── main.dart ← redirection selon le rôle
{
"email": "exemple@domaine.com",
"role": "lecteur",
"isApproved": false
}
Lorsqu’un utilisateur se connecte
- Firebase Auth vérifie l’email et le mot de passe.
- L’application consulte Firestore pour vérifier :
- s’il existe un profil correspondant à cet UID ;
- si isApproved == true.
- En fonction du rôle :
- Admin → interface d’administration (AdminDashboardView)
- Auteur ou Lecteur → interface principale (ListeLivresView)
- Non validé → message d’attente de validation.
Modèle UserModel étendu
Fichier user_model.dart
class UserModel {
final String uid;
final String email;
final String role; // 'admin', 'auteur', 'lecteur'
final bool isApproved; // true si l'utilisateur est approuvé
final DateTime createdAt;
UserModel({
required this.uid,
required this.email,
required this.role,
required this.isApproved,
required this.createdAt,
});
factory UserModel.fromFirebaseUser(User user, {Map<String, dynamic>? additionalData}) {
return UserModel(
uid: user.uid,
email: user.email ?? "",
role: additionalData?['role'] ?? 'lecteur', // Par défaut 'lecteur'
isApproved: additionalData?['isApproved'] ?? false, // Par défaut false
createdAt: additionalData?['createdAt']?.toDate() ?? DateTime.now(),
);
}
Map<String, dynamic> toMap() {
return {
'uid': uid,
'email': email,
'role': role,
'isApproved': isApproved,
'createdAt': createdAt,
};
}
}
Service Firestore pour gérer les utilisateurs
Fichier user_service.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class UserService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
// Sauvegarder les données utilisateur dans Firestore
Future<void> saveUserData(UserModel user) async {
await _firestore.collection('users').doc(user.uid).set(user.toMap());
}
// Récupérer les données utilisateur
Future<UserModel?> getUserData(String uid) async {
final doc = await _firestore.collection('users').doc(uid).get();
if (doc.exists) {
final data = doc.data()!;
return UserModel(
uid: uid,
email: data['email'],
role: data['role'],
isApproved: data['isApproved'],
createdAt: data['createdAt'].toDate(),
);
}
return null;
}
// Récupérer tous les utilisateurs en attente d'approbation
Stream<List<UserModel>> getPendingUsers() {
return _firestore
.collection('users')
.where('isApproved', isEqualTo: false)
.snapshots()
.map((snapshot) => snapshot.docs.map((doc) {
final data = doc.data();
return UserModel(
uid: doc.id,
email: data['email'],
role: data['role'],
isApproved: data['isApproved'],
createdAt: data['createdAt'].toDate(),
);
}).toList());
}
// Approuver un utilisateur
Future<void> approveUser(String uid) async {
await _firestore.collection('users').doc(uid).update({
'isApproved': true,
});
}
// Refuser un utilisateur
Future<void> rejectUser(String uid) async {
await _firestore.collection('users').doc(uid).delete();
// Optionnel : supprimer aussi le compte Firebase Auth
// await FirebaseAuth.instance.deleteUser(uid);
}
}
authController modifié
Fichier auth_controller.dart
class AuthController {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: ['email', 'profile'],
);
final UserService _userService = UserService();
// Inscription avec rôle
Future register({
required String email,
required String password,
required String role // 'auteur' ou 'lecteur'
}) async {
try {
if (role != 'auteur' && role != 'lecteur') {
return "Rôle invalide";
}
// Créer l'utilisateur Firebase
final userCredential = await _auth.createUserWithEmailAndPassword(
email: email.trim(),
password: password.trim(),
);
// Sauvegarder les données dans Firestore
final userModel = UserModel(
uid: userCredential.user!.uid,
email: email.trim(),
role: role,
isApproved: false, // Non approuvé par défaut
createdAt: DateTime.now(),
);
await _userService.saveUserData(userModel);
return null;
} on FirebaseAuthException catch (e) {
return getErrorMessage(e);
}
}
// Connexion avec vérification d'approbation
Future login({required String email, required String password}) async {
try {
final userCredential = await _auth.signInWithEmailAndPassword(
email: email.trim(),
password: password.trim(),
);
// Vérifier si l'utilisateur est approuvé
final userData = await _userService.getUserData(userCredential.user!.uid);
if (userData == null) {
await _auth.signOut();
return "Compte non trouvé dans la base de données";
}
if (!userData.isApproved) {
await _auth.signOut();
return "Votre compte est en attente d'approbation par l'administrateur";
}
return null;
} on FirebaseAuthException catch (e) {
return getErrorMessage(e);
}
}
// Récupérer le UserModel complet
Future<UserModel?> getCurrentUserModel() async {
final user = _auth.currentUser;
if (user != null) {
return await _userService.getUserData(user.uid);
}
return null;
}
// Stream pour écouter les changements d'état d'authentification ET d'approbation
Stream get authStateChanges {
return _auth.authStateChanges().asyncMap((user) async {
if (user != null) {
return await _userService.getUserData(user.uid);
}
return null;
});
}
// ... reste du code existant (google signin, logout, etc.)
}
Vue d’inscription modifiée
Fichier signup_view.dart
class SignUpView extends StatefulWidget {
const SignUpView({super.key});
@override
State createState() => _SignUpViewState();
}
class _SignUpViewState extends State {
final auth = AuthController();
final emailController = TextEditingController();
final passwordController = TextEditingController();
String? errorMessage;
bool isLoading = false;
String selectedRole = 'lecteur'; // Rôle par défaut
Future register() async {
setState(() {
isLoading = true;
errorMessage = null;
});
final error = await auth.register(
email: emailController.text,
password: passwordController.text,
role: selectedRole,
);
if (error != null) {
setState(() => errorMessage = error);
} else {
// Afficher un message de succès
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Inscription réussie"),
content: const Text("Votre compte est en attente d'approbation par l'administrateur."),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context); // Fermer la dialog
Navigator.pop(context); // Retour à la connexion
},
child: const Text("OK"),
),
],
),
);
}
setState(() => isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.symmetric(horizontal: 25),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF11998E), Color(0xFF38EF7D)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Center(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
const Text(
"Créer un compte",
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Color(0xFF11998E),
),
),
const SizedBox(height: 25),
TextField(
controller: emailController,
decoration: InputDecoration(
labelText: "Email",
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
const SizedBox(height: 15),
TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: "Mot de passe",
prefixIcon: const Icon(Icons.lock_outline),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
const SizedBox(height: 15),
// Sélection du rôle
DropdownButtonFormField(
value: selectedRole,
decoration: InputDecoration(
labelText: "Rôle",
prefixIcon: const Icon(Icons.person_outline),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
items: [
DropdownMenuItem(
value: 'lecteur',
child: Text('Lecteur'),
),
DropdownMenuItem(
value: 'auteur',
child: Text('Auteur'),
),
],
onChanged: (value) {
setState(() {
selectedRole = value!;
});
},
),
const SizedBox(height: 20),
if (errorMessage != null)
Text(
errorMessage!,
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isLoading ? null : register,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
backgroundColor: const Color(0xFF11998E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text(
"S'inscrire",
style: TextStyle(fontSize: 18),
),
),
),
const SizedBox(height: 10),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Déjà un compte ? Se connecter"),
),
],
),
),
),
),
),
);
}
}
Vue Admin pour gérer les approbations
Fichier admindashboard_view.dart
class AdminDashboard extends StatelessWidget {
final UserService _userService = UserService();
AdminDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Tableau de bord Admin"),
backgroundColor: Colors.red,
),
body: StreamBuilder>(
stream: _userService.getPendingUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Erreur: ${snapshot.error}'));
}
final pendingUsers = snapshot.data ?? [];
if (pendingUsers.isEmpty) {
return const Center(child: Text("Aucune demande d'inscription en attente"));
}
return ListView.builder(
itemCount: pendingUsers.length,
itemBuilder: (context, index) {
final user = pendingUsers[index];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
title: Text(user.email),
subtitle: Text('Rôle: ${user.role} - Inscrit le: ${DateFormat('dd/MM/yyyy').format(user.createdAt)}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.check, color: Colors.green),
onPressed: () async {
await _userService.approveUser(user.uid);
},
),
IconButton(
icon: const Icon(Icons.close, color: Colors.red),
onPressed: () async {
await _userService.rejectUser(user.uid);
},
),
],
),
),
);
},
);
},
),
);
}
}
Modifications dans main.dart
Fichier auth_controller.dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilder(
stream: AuthController().authStateChanges,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
final userModel = snapshot.data;
if (userModel == null) {
return const LoginView();
}
// Vérifier si l'utilisateur est approuvé
if (!userModel.isApproved) {
return const WaitingApprovalView();
}
// Rediriger selon le rôle
switch (userModel.role) {
case 'admin':
return const AdminDashboard();
case 'auteur':
return const AuthorDashboard();
case 'lecteur':
return const ReaderDashboard();
default:
return const LoginView();
}
},
),
);
}
}
// Vue d'attente d'approbation
class WaitingApprovalView extends StatelessWidget {
const WaitingApprovalView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.schedule, size: 80, color: Colors.orange),
const SizedBox(height: 20),
const Text(
"En attente d'approbation",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
const Text(
"Votre compte est en cours de validation par l'administrateur",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: () => AuthController().logout(),
child: const Text("Se déconnecter"),
),
],
),
),
);
}
}
Configuration Firestore
Règles Firestore
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null &&
(request.auth.uid == userId ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin');
}
}
}
contrôleur d’authentification (auth_controller.dart)
Fichier auth_controller.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthController with ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
User? get currentUser => _auth.currentUser;
// Inscription
Future<String?> signUp(String email, String password, String role) async {
try {
// Création du compte dans Firebase Authentication
UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
// Ajout du profil utilisateur dans Firestore
await _firestore.collection('users').doc(userCredential.user!.uid).set({
'email': email,
'role': role,
'isApproved': false, // Non validé par défaut
'createdAt': FieldValue.serverTimestamp(),
});
// Déconnexion automatique après l'inscription
await _auth.signOut();
return "Inscription réussie \nVotre compte est en attente de validation par l’administrateur.";
} on FirebaseAuthException catch (e) {
if (e.code == 'email-already-in-use') {
return "Cet email est déjà utilisé.";
} else if (e.code == 'weak-password') {
return "Mot de passe trop faible.";
} else if (e.code == 'invalid-email') {
return "Adresse email invalide.";
}
return "Erreur d'authentification : ${e.message}";
} catch (e) {
return "Erreur inattendue : $e";
}
}
// Connexion
Future<String?> login(String email, String password) async {
try {
UserCredential userCredential =
await _auth.signInWithEmailAndPassword(email: email, password: password);
// Vérifie si l’utilisateur a un profil dans Firestore
final doc = await _firestore.collection('users').doc(userCredential.user!.uid).get();
if (!doc.exists) {
await _auth.signOut();
return "Profil utilisateur introuvable.";
}
final userData = doc.data();
if (userData == null || userData['isApproved'] == false) {
await _auth.signOut();
return "Votre compte est en attente de validation par l’administrateur.";
}
return null; // Connexion réussie
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
return "Aucun utilisateur trouvé avec cet email.";
} else if (e.code == 'wrong-password') {
return "Mot de passe incorrect.";
} else if (e.code == 'invalid-email') {
return "Adresse email invalide.";
}
return "Erreur d'authentification : ${e.message}";
} catch (e) {
return "Erreur inattendue : $e";
}
}
// Déconnexion
Future<void> logout() async {
await _auth.signOut();
}
// Écouteur d’état de connexion
Stream<User?> authStateChanges() {
return _auth.authStateChanges();
}
}
Gestion de la redirection automatique (main.dart)
Fichier main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:provider/provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'firebase_options.dart';
import 'controllers/livre_controller.dart';
import 'controllers/auth_controller.dart';
import 'views/liste_livres_view_recherche.dart';
import 'views/login_view.dart';
import 'views/admin_dashboard_view.dart'; // à ajouter
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<LivreController>(
create: (_) => LivreController(),
),
ChangeNotifierProvider<AuthController>(
create: (_) => AuthController(),
),
],
child: MaterialApp(
title: 'Ma Bibliothèque',
theme: ThemeData(
useMaterial3: false,
primaryColor: const Color(0xFF4CAF50),
),
debugShowCheckedModeBanner: false,
home: AuthWrapper(), // redirection automatique selon le rôle
),
);
}
}
class AuthWrapper extends StatelessWidget {
const AuthWrapper({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
// Chargement initial
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()));
}
// Si pas connecté → page de connexion
if (!snapshot.hasData) {
return LoginView();
}
// Utilisateur connecté
final user = snapshot.data!;
final usersRef = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: usersRef.doc(user.uid).get(),
builder: (context, userSnapshot) {
if (userSnapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()));
}
if (!userSnapshot.hasData || !userSnapshot.data!.exists) {
return const Scaffold(
body: Center(
child: Text(" Profil utilisateur introuvable."),
),
);
}
final userData =
userSnapshot.data!.data() as Map<String, dynamic>? ?? {};
final role = userData['role'] ?? 'lecteur';
final isApproved = userData['isApproved'] ?? false;
// Compte non validé
if (!isApproved) {
return const Scaffold(
body: Center(
child: Text(
" Votre compte est en attente de validation par l’administrateur.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
),
);
}
// Si admin → dashboard
if (role == 'admin') {
return const AdminDashboardView();
}
// Sinon → page de livres
return const ListeLivresView();
},
);
},
);
}
}
Règles de sécurité Firestore
Regles
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
// Création (inscription) : tout utilisateur connecté peut créer son doc
allow create: if request.auth != null;
// Lecture :
// - un utilisateur peut lire son propre profil
// - ou un admin peut lire tous les profils
allow read: if request.auth != null && (
request.auth.uid == userId ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin'
);
// Mise à jour / suppression :
// - un utilisateur peut modifier son propre profil
// - un admin peut modifier/supprimer n’importe qui
allow update, delete: if request.auth != null && (
request.auth.uid == userId ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin'
);
}
// Interdire le reste
match /{document=**} {
allow read, write: if false;
}
}
}
Ajout d’un utilisateur et enregistrement simultané dans Firebase Authentication et Cloud Firestore
-
Objectif pédagogique
- À la fin de cette section, vous serez capable de :
- Créer un utilisateur dans Firebase Authentication pour gérer son email et mot de passe.
- Enregistrer ses informations complémentaires (rôle, état de validation, etc.) dans Cloud Firestore.
- Comprendre pourquoi ces deux enregistrements sont nécessaires et comment ils interagissent.
-
Pourquoi deux enregistrements ?
- Firebase Authentication et Firestore ont des rôles bien distincts :
- Ainsi, Auth garantit que l’utilisateur existe et peut se connecter,tandis que Firestore décrit qui il est et ce qu’il a le droit de faire dans l’application.
-
Étapes du processus d’inscription
- Lorsqu’un utilisateur s’inscrit, les opérations suivantes s’exécutent dans cet ordre :
- Création du compte dans Firebase Authentication
- → Auth crée un identifiant unique (UID) pour cet utilisateur.
- Enregistrement dans Firestore
- → On ajoute un document users/{uid} contenant des informations complémentaires :
- role (auteur / lecteur)
- isApproved (true/false pour validation par admin)
- Déconnexion automatique après inscription
- → L’utilisateur ne peut pas se connecter tant qu’il n’a pas été approuvé.
| Service | Rôle principal | Exemple de données stockées |
|---|---|---|
| Firebase Authentication | Gère la sécurité, la connexion, la vérification d’identité | Email, mot de passe, UID |
| Cloud Firestore | Stocke les informations métier liées à l’utilisateur | Rôle (admin, auteur, lecteur), statut de validation, préférences, etc. |

