I am working on a Flutter project where I am trying to implement authentication using Amazon Cognito. I am facing issues with the authentication flow, and the app is stuck in an endless waiting state when attempting to authenticate users. I am using GoRouter for routing and StreamBuilder / FutureBuilder to manage authentication state. However, the user is not being properly authenticated, and the page doesn’t load as expected.
Here are the files I am working with:
- main.dart (App entry point, router setup)
- auth_controller.dart (Handles authentication logic)
- login_controller.dart (Handles login logic)
- sign_up_controller.dart (Handles signup logic)
- login_screen.dart (Login UI)
- auth_service.dart (Interacts with Cognito for authentication)
- login_form.dart (Login form widget)
The problem: When I try to authenticate, it stays in the loading state indefinitely. I cannot access the login page, and it seems the Stream in authStateChanges is not emitting any data, causing the app to remain stuck in the waiting state.
The following code snippets illustrate the setup:
main
import 'package:checkee/theme/theme.dart';
import 'package:flutter/material.dart';
import 'core/router.dart'; // Importer router.dart pour gérer la logique de routage
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Checkee',
theme: checkeeTheme,
routerConfig: appRouter, // Utiliser appRouter configuré dans router.dart
);
}
}
auth_controller
import 'dart:async';
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
class AuthController {
final userPool = CognitoUserPool(
'eu-west-1_eYRgcALYR', // Remplace par ton User Pool ID
'rih16pulnrf9ihflpqiobjomk', // Remplace par ton App Client ID
);
final _authStateController = StreamController<CognitoUser?>.broadcast();
// Connexion de l'utilisateur
Future<CognitoUserSession?> signInWithEmailPassword(String email, String password) async {
try {
final cognitoUser = CognitoUser(email, userPool);
final authDetails = AuthenticationDetails(
username: email,
password: password,
);
final session = await cognitoUser.authenticateUser(authDetails);
_authStateController.add(cognitoUser); // Envoie l'utilisateur dans le stream
return session;
} catch (e) {
print("Erreur de connexion avec Cognito : $e");
_authStateController.add(null); // Envoie null en cas d'échec
return null;
}
}
// Fonction pour surveiller l'état de l'authentification
Stream<CognitoUser?> authStateChanges() {
return _authStateController.stream;
}
// Méthode pour se déconnecter
Future<void> signOut() async {
_authStateController.add(null); // Met à jour le stream avec null pour indiquer la déconnexion
}
// Récupère le type d'utilisateur (locataire, propriétaire, etc.)
Future<String> getUserType(String username) async {
final cognitoUser = CognitoUser(username, userPool);
final session = await cognitoUser.getSession();
final userGroups = await getUserGroups(session!);
if (userGroups.contains('Propriétaire')) {
return 'Propriétaire';
} else if (userGroups.contains('Locataire')) {
return 'Locataire';
}
return 'Locataire'; // Par défaut
}
// Récupère les groupes de l'utilisateur
Future<List<String>> getUserGroups(CognitoUserSession session) async {
final idToken = session.idToken;
final claims = idToken.payload;
final groups = claims['cognito:groups'] as List<dynamic>? ?? [];
return groups.map((group) => group.toString()).toList();
}
}
login_controller
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../services/auth_service.dart'; // Assurez-vous que AuthService est correctement importé
class LoginController {
final TextEditingController emailController;
final TextEditingController passwordController;
LoginController({
required this.emailController,
required this.passwordController,
});
Future<void> login(BuildContext context) async {
try {
final session = await AuthService().signInWithEmailPassword(
emailController.text,
passwordController.text,
);
if (session != null) {
// Vérifie le groupe de l'utilisateur via Cognito et redirige en fonction du rôle
final userGroups = await AuthService().getUserGroups(session);
if (userGroups.contains('Admin')) {
context.go('/adminHome'); // Rediriger vers la page Admin
} else if (userGroups.contains('Propriétaire')) {
context.go('/ownerHome'); // Rediriger vers la page Propriétaire
} else if (userGroups.contains('Locataire')) {
context.go('/tenantHome'); // Rediriger vers la page Locataire
} else {
context.go('/'); // Redirection par défaut si aucun groupe spécifique
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Email ou mot de passe invalide')),
);
}
} catch (e) {
print("Erreur de connexion avec Cognito : $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur de connexion : $e')),
);
}
}
void dispose() {
emailController.dispose();
passwordController.dispose();
}
}
sign_up_controller
import '../services/auth_service.dart'; // Assurez-vous que AuthService est importé
class SignUpController {
final AuthService _authService = AuthService();
Future<String?> signUp({
required String email,
required String password,
required String firstName,
required String lastName,
required String phoneNumber,
String? address,
String? tvaNumber,
required String userType,
}) async {
try {
// Appel à la méthode signUpWithEmailPassword d'AuthService pour créer un utilisateur
final errorMessage = await _authService.signUpWithEmailPassword(email, password);
if (errorMessage != null) {
return errorMessage; // Retourner un message d'erreur si l'inscription échoue
}
// Ajouter les autres informations utilisateur si l'inscription réussit
// Par exemple, enregistrer ces informations dans une base de données
return null; // Inscription réussie
} catch (e) {
print("Erreur lors de l'inscription : $e");
return 'Erreur lors de l\'inscription'; // Message générique en cas d'échec
}
}
}
login_screen
import 'package:flutter/material.dart';
import 'package:checkee/features/auth/controllers/login_controller.dart'; // Assurez-vous d'avoir la référence du login_controller.dart
class LoginScreen extends StatelessWidget {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
final loginController = LoginController(
emailController: emailController,
passwordController: passwordController,
);
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: emailController,
decoration: InputDecoration(labelText: 'Email'),
),
TextField(
controller: passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
ElevatedButton(
onPressed: () async {
await loginController.login(context); // Appel de la méthode login() de LoginController
},
child: Text('Login'),
),
],
),
),
);
}
}
auth_service
import 'dart:async';
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
class AuthService {
final userPool = CognitoUserPool(
'eu-west-1_eYRgcALYR', // Remplace par ton User Pool ID
'rih16pulnrf9ihflpqiobjomk', // Remplace par ton App Client ID
);
final _authStateController = StreamController<CognitoUser?>.broadcast();
// Connexion de l'utilisateur
Future<CognitoUserSession?> signInWithEmailPassword(String email, String password) async {
try {
final cognitoUser = CognitoUser(email, userPool);
final authDetails = AuthenticationDetails(
username: email,
password: password,
);
final session = await cognitoUser.authenticateUser(authDetails);
_authStateController.add(cognitoUser); // Envoie l'utilisateur dans le stream
return session;
} catch (e) {
print("Erreur de connexion avec Cognito : $e");
_authStateController.add(null); // Envoie null en cas d'échec
return null;
}
}
// Inscription de l'utilisateur
Future<String?> signUpWithEmailPassword(String email, String password) async {
try {
final userAttributes = [
AttributeArg(name: 'email', value: email),
];
final signUpResult = await userPool.signUp(email, password, userAttributes: userAttributes);
if (signUpResult != null) {
return null; // Inscription réussie
} else {
return 'Erreur lors de la création de l\'utilisateur';
}
} catch (e) {
print("Erreur lors de l'inscription : $e");
return e.toString();
}
}
// Récupère la session d'un utilisateur avec son email
Future<CognitoUserSession?> getSession(String email) async {
final cognitoUser = CognitoUser(email, userPool);
try {
final session = await cognitoUser.getSession();
return session; // Retourne la session si disponible
} catch (e) {
print("Erreur lors de la récupération de la session : $e");
return null; // Retourne null en cas d'erreur
}
}
// Méthode pour récupérer les groupes de l'utilisateur à partir de la session
Future<List<String>> getUserGroups(CognitoUserSession session) async {
try {
final idToken = session.idToken; // On accède au ID token
final claims = idToken.payload; // Récupère les claims de l'ID token
// Récupérer les groupes à partir des claims (s'il y en a)
final groups = claims['cognito:groups'] as List<dynamic>? ?? [];
return groups.map((group) => group.toString()).toList(); // Convertir en liste de chaînes
} catch (e) {
print("Erreur de récupération des groupes : $e");
return [];
}
}
// Fonction de déconnexion
Future<void> signOut() async {
_authStateController.add(null); // Envoie null dans le stream lors de la déconnexion
}
// Fonction pour surveiller l'état de l'authentification
Stream<CognitoUser?> authStateChanges() {
return _authStateController.stream;
}
}