LarpLand uses Firebase Authentication to provide secure email/password authentication with automatic session management through the AuthSession singleton.
Overview
The authentication system provides:
Email/password registration with automatic user profile creation
Secure login with Firebase Auth and ID token generation
Session persistence with AuthSession singleton
Role-based routing to User or Admin interfaces
Automatic profile sync between Firebase Auth and Firestore
Registration Flow
User registration is handled by the register function in lib/service/register.dart:
lib/service/register.dart
Future < User > register ( String name, String email, String password) async {
FirebaseBackend . ensureInitialized ();
try {
final credential = await FirebaseBackend .auth. createUserWithEmailAndPassword (
email : email. trim (),
password : password,
);
final createdUser = credential.user;
if (createdUser == null ) {
throw const AppError (
code : 'auth.user_not_created' ,
message : 'No se pudo crear el usuario en Firebase Auth.' ,
);
}
final trimmedName = name. trim ();
if (trimmedName.isNotEmpty) {
await createdUser. updateDisplayName (trimmedName);
}
final profile = await FirebaseBackend . ensureUserProfile (
firebaseUser : createdUser,
fallbackName : trimmedName,
);
await FirebaseBackend .auth. signOut ();
return User . fromJson (profile);
} on fb_auth. FirebaseAuthException catch (e) {
throw AppError (
code : 'auth. ${ e . code } ' ,
message : _firebaseAuthMessage (e),
cause : e,
);
}
}
Registration Process
Create Firebase Auth user with email and password
Update display name in Firebase Auth
Create Firestore user profile via ensureUserProfile
Sign out to force login after registration
Return User model with profile data
New users are automatically assigned rol: 0 (User role) in their Firestore profile.
Login Flow
User login is handled by the login function in lib/service/login.dart:
Future < Login > login ( String email, String password) async {
FirebaseBackend . ensureInitialized ();
try {
final credential = await FirebaseBackend .auth. signInWithEmailAndPassword (
email : email. trim (),
password : password,
);
final user = credential.user;
if (user == null ) {
throw const AppError (
code : 'auth.user_not_found' ,
message : 'No se pudo recuperar el usuario autenticado.' ,
);
}
final profile = await FirebaseBackend . ensureUserProfile (firebaseUser : user);
final rol = _asInt (profile[ 'rol' ]) ?? 0 ;
final userId = _asInt (profile[ 'id' ]);
if (userId == null ) {
throw const AppError (
code : 'auth.profile_missing_user_id' ,
message : 'El perfil del usuario no contiene un id numerico.' ,
);
}
final idToken = await user. getIdToken ();
final result = Login (
status : 'success' ,
rol : rol,
message : 'Login exitoso' ,
userId : userId,
token : idToken,
);
AuthSession . bind (
idToken : idToken,
uid : user.uid,
sessionUserId : userId,
sessionRol : rol,
);
return result;
} on fb_auth. FirebaseAuthException catch (e) {
throw AppError (
code : 'auth. ${ e . code } ' ,
message : _firebaseAuthMessage (e),
cause : e,
);
}
}
Login Process
Authenticate with Firebase using email/password
Retrieve user profile from Firestore
Extract role and user ID from profile
Generate ID token for API authentication
Bind session via AuthSession.bind()
Return Login result with user data and token
Session Management
The AuthSession class provides singleton session state:
lib/service/auth_session.dart
class AuthSession {
static String ? token;
static String ? firebaseUid;
static int ? userId;
static int ? rol;
static void bind ({
String ? idToken,
String ? uid,
int ? sessionUserId,
int ? sessionRol,
}) {
token = idToken;
firebaseUid = uid;
userId = sessionUserId;
rol = sessionRol;
}
static Future < void > syncFromFirebase () async {
final user = fb_auth. FirebaseAuth .instance.currentUser;
if (user == null ) {
clearLocal ();
return ;
}
final profile = await FirebaseBackend . ensureUserProfile (firebaseUser : user);
bind (
idToken : await user. getIdToken (),
uid : user.uid,
sessionUserId : profile[ 'id' ] is int
? profile[ 'id' ] as int
: int . tryParse ( ' ${ profile [ 'id' ] ?? '' } ' ),
sessionRol : profile[ 'rol' ] is int
? profile[ 'rol' ] as int
: int . tryParse ( ' ${ profile [ 'rol' ] ?? '' } ' ),
);
}
static Future < void > signOut () async {
try {
await FirebaseBackend .auth. signOut ();
} finally {
clearLocal ();
}
}
static void clearLocal () {
token = null ;
firebaseUid = null ;
userId = null ;
rol = null ;
}
}
Session Properties
token - Firebase ID token for authenticated requests
firebaseUid - Firebase Authentication UID
userId - Numeric user ID from Firestore profile
rol - User role (0 = User, 1 = Admin)
User Profile Management
The ensureUserProfile method creates or updates user profiles in Firestore:
lib/service/firebase_backend.dart
static Future < Map < String , dynamic >> ensureUserProfile ({
required fb_auth. User firebaseUser,
String ? fallbackName,
}) async {
ensureInitialized ();
try {
final existing = await users
. where ( 'firebase_uid' , isEqualTo : firebaseUser.uid)
. limit ( 1 )
. get ();
if (existing.docs.isNotEmpty) {
final ref = existing.docs.first.reference;
final current = existing.docs.first. data ();
final merged = < String , dynamic > {
...current,
'email' : firebaseUser.email ?? current[ 'email' ] ?? '' ,
if ((firebaseUser.displayName ?? '' ). trim ().isNotEmpty)
'name' : firebaseUser.displayName ! . trim ()
else if ((fallbackName ?? '' ). trim ().isNotEmpty)
'name' : fallbackName ! . trim (),
'firebase_uid' : firebaseUser.uid,
'updated_at' : FieldValue . serverTimestamp (),
};
await ref. set (merged, SetOptions (merge : true ));
return normalizeMap (merged);
}
final userId = await nextNumericId ( 'users' );
final name = (firebaseUser.displayName ?? fallbackName ?? '' ). trim ();
final payload = < String , dynamic > {
'id' : userId,
'name' : name.isEmpty ? 'Usuario' : name,
'email' : firebaseUser.email ?? '' ,
'rol' : 0 ,
'firebase_uid' : firebaseUser.uid,
'created_at' : FieldValue . serverTimestamp (),
'updated_at' : FieldValue . serverTimestamp (),
};
await users. doc (firebaseUser.uid). set (payload, SetOptions (merge : true ));
return normalizeMap (payload);
} on FirebaseException catch (e) {
throw AppError (
code : 'firestore. ${ e . code } ' ,
message : _firestoreErrorMessage (e),
cause : e,
);
}
}
Profile Fields
id - Numeric user ID (auto-generated)
name - User display name
email - User email address
rol - User role (0 or 1)
firebase_uid - Firebase Auth UID
created_at - Profile creation timestamp
updated_at - Last update timestamp
Error Handling
Firebase Auth errors are mapped to user-friendly Spanish messages:
String _firebaseAuthMessage (fb_auth. FirebaseAuthException e) {
switch (e.code) {
case 'invalid-credential' :
case 'wrong-password' :
case 'user-not-found' :
return 'Correo o contrasena incorrectos.' ;
case 'invalid-email' :
return 'El correo electronico no es valido.' ;
case 'too-many-requests' :
return 'Demasiados intentos. Intenta de nuevo mas tarde.' ;
case 'network-request-failed' :
return 'Sin conexion a internet.' ;
case 'email-already-in-use' :
return 'El correo ya esta registrado.' ;
case 'weak-password' :
return 'La contrasena es demasiado debil.' ;
default :
return e.message ?? 'No se pudo iniciar sesion.' ;
}
}
Role-Based Routing
After successful login, users are routed based on their role:
lib/view/login/login.dart
if (futureResult.rol == 0 ) {
Navigator . push (
context,
MaterialPageRoute (
builder : (context) => HomeScreen (userId : futureResult.userId),
),
);
} else if (futureResult.rol == 1 ) {
Navigator . push (
context,
MaterialPageRoute (
builder : (context) => AdminHome (userId : futureResult.userId),
),
);
}
rol = 0 : Routes to HomeScreen (User interface)
rol = 1 : Routes to AdminHome (Admin interface)
See User Roles for more details on role-based access control.
User Roles Learn about User vs Admin roles
Firebase Backend Firebase integration details