Skip to main content
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

  1. Create Firebase Auth user with email and password
  2. Update display name in Firebase Auth
  3. Create Firestore user profile via ensureUserProfile
  4. Sign out to force login after registration
  5. 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:
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

  1. Authenticate with Firebase using email/password
  2. Retrieve user profile from Firestore
  3. Extract role and user ID from profile
  4. Generate ID token for API authentication
  5. Bind session via AuthSession.bind()
  6. 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

Build docs developers (and LLMs) love