Authentication

Khadem provides a comprehensive authentication system with support for JWT and token-based authentication. The system is built with security, flexibility, and ease of use in mind.

Multi-Driver Support

JWT, Token, and extensible custom drivers

Secure by Default

Built-in security features and best practices

Flexible Configuration

Guard-based authentication with multiple providers

Setup and Configuration

Setting up authentication in your Khadem application involves registering the AuthServiceProvider and configuring your authentication guards and providers.
dart

// main.dart or app bootstrap
import 'package:khadem/khadem.dart';

void main() async {
  // Initialize Khadem application
  await Khadem.initialize();

  // Register Auth Service Provider
  Khadem.container.register(AuthServiceProvider());

  // Boot the application
  await Khadem.boot();

  print('Khadem app with authentication ready!');
}
dart

// config/auth.dart
const authConfig = {
  'default': 'users',
  'guards': {
    'users': {'driver': 'token', 'provider': 'users'},
    'admins': {'driver': 'token', 'provider': 'admins'},
  },
  'providers': {
    'users': {
      'table': 'users',
      'primary_key': 'id',
      'fields': ['email'],
    },
    'admins': {
      'table': 'admins',
      'primary_key': 'id',
      'fields': ['email'],
    },
  },
};

AuthManager - Main Authentication Interface

The AuthManager provides a unified interface for all authentication operations. It supports multiple guards and drivers for flexible authentication scenarios.
dart

import 'package:khadem/khadem.dart';

class AuthController {
  final AuthManager auth;

  AuthController.singleton({String? guard})
      : auth = AuthManager(guard: guard);

  static AuthController instance = AuthController.singleton();
  factory AuthController() => instance;

  Future<Map<String, dynamic>> login(Request req, Response res) async {
    final data = await req.validate({
      'email': 'required|email',
      'password': 'required|min:6',
    });

    // AuthManager.attempt() returns AuthResponse
    final authResponse = await auth.attempt(data);

    return {
      'message': 'Login successful',
      'data': authResponse.toMap(), // Contains user, access_token, refresh_token, etc.
    };
  }

  Future<Map<String, dynamic>> register(Request req, Response res) async {
    final data = await req.validate({
      'name': 'required',
      'email': 'required|email',
      'password': 'required|min:6|confirmed',
    });

    data['password'] = HashHelper.hash(data['password']);

    final user = User()..fromJson(data);
    await user.save();

    // Authenticate after registration
    final authResponse = await auth.attempt({
      'email': data['email'],
      'password': req.input('password'),
    });

    return {
      'message': 'User registered successfully',
      'data': authResponse.toMap(),
    };
  }

  Future<Map<String, dynamic>> profile(Request req, Response res) async {
    // Get authenticated user from request
    final authenticatable = req.authenticatable;
    if (authenticatable == null) {
      throw AuthException('User not authenticated');
    }

    return {
      'message': 'Profile retrieved',
      'data': {'user': authenticatable.toAuthArray()},
    };
  }
}
dart

import 'package:khadem/khadem.dart';

class AuthController {
  static Future<void> login(Request req, Response res) async {
    final data = await req.validate({
      'email': 'required|email',
      'password': 'required|min:6',
    });

    final auth = AuthManager();
    final authResponse = await auth.attempt(data);

    res.sendJson({
      'message': 'Login successful',
      'data': {
        'access_token': authResponse.accessToken,
        'refresh_token': authResponse.refreshToken,
        'token_type': authResponse.tokenType,
        'expires_in': authResponse.expiresIn,
        'user': authResponse.user,
      }
    });
  }

  static Future<void> verifyToken(Request req, Response res) async {
    // Get token from Authorization header
    final authHeader = req.header('authorization');
    final token = authHeader?.replaceFirst('Bearer ', '');

    if (token == null) {
      return res.status(401).sendJson({'error': 'Token required'});
    }

    final auth = AuthManager();
    final user = await auth.user(token);

    res.sendJson({
      'message': 'Token is valid',
      'data': {'user': user.toAuthArray()},
    });
  }
}

Web Authentication

For web applications, Khadem provides session-based authentication with WebAuthService and WebAuthMiddleware for protecting routes and managing user sessions.
dart

import 'package:khadem/khadem.dart';

class WebAuthController {
  WebAuthController.singleton();
  static WebAuthController instance = WebAuthController.singleton();
  factory WebAuthController() => instance;

  Future<void> showLogin(Request req, Response res) async {
    await res.view('login');
  }

  Future<void> showRegister(Request req, Response res) async {
    await res.view('register');
  }

  Future<void> login(Request req, Response res) async {
    final data = await req.validate({
      'email': 'required|email',
      'password': 'required|min:6',
      'remember': 'nullable|boolean',
    });

    // Use AuthManager with 'web' guard for session-based auth
    final auth = AuthManager(guard: 'web');
    final authResponse = await auth.attempt({
      'email': data['email'],
      'password': data['password'],
    });

    // Store user and tokens in session
    req.session.set('user_id', authResponse.user['id']);
    req.session.set('access_token', authResponse.accessToken);
    
    if (authResponse.refreshToken != null) {
      req.session.set('refresh_token', authResponse.refreshToken);
    }

    // Handle remember me
    if (data['remember'] == true && authResponse.refreshToken != null) {
      res.cookieHandler.set(
        'remember_token',
        authResponse.refreshToken!,
        maxAge: const Duration(days: 30),
        httpOnly: true,
        secure: true,
      );
    }

    final intended = req.session.get('url.intended') ?? '/dashboard';
    req.session.remove('url.intended');
    res.redirect(intended.toString());
  }

  Future<void> register(Request req, Response res) async {
    final data = await req.validate({
      'name': 'required',
      'email': 'required|email',
      'password': 'required|min:6|confirmed',
    });

    data['password'] = HashHelper.hash(data['password']);
    final user = User()..fromJson(data);
    await user.save();

    // Auto-login after registration
    final auth = AuthManager(guard: 'web');
    final authResponse = await auth.attempt({
      'email': data['email'],
      'password': req.input('password'),
    });

    req.session.set('user_id', authResponse.user['id']);
    req.session.set('access_token', authResponse.accessToken);

    res.redirect('/dashboard');
  }

  Future<void> dashboard(Request req, Response res) async {
    // User is already authenticated by WebAuthMiddleware
    final authenticatable = req.authenticatable;
    
    if (authenticatable == null) {
      return res.redirect('/login');
    }

    final user = authenticatable.toAuthArray();
    await res.view('dashboard', data: {'user': user});
  }

  Future<void> logout(Request req, Response res) async {
    // Get current token to invalidate
    final token = req.session.get('access_token') as String?;
    
    if (token != null) {
      try {
        final auth = AuthManager(guard: 'web');
        await auth.logout(token);
      } catch (e) {
        // Log error but continue logout
      }
    }

    // Clear session and cookies
    req.session.remove('user_id');
    req.session.remove('access_token');
    req.session.remove('refresh_token');
    res.cookieHandler.delete('remember_token');
    
    req.session.flash('message', 'Successfully logged out');
    res.redirect('/login');
  }
}
dart

import 'package:khadem/khadem.dart';

// Use the built-in WebAuthMiddleware from Khadem
class WebAuthMiddleware {
  /// Middleware for authenticated routes
  static Middleware auth({
    String redirectTo = '/login',
    List<String> except = const [],
    String guard = 'web',
  }) {
    return WebAuthMiddleware.create(
      redirectTo: redirectTo,
      except: except,
      guard: guard,
    );
  }

  /// Middleware for guest-only routes (redirects authenticated users)
  static Middleware guest({
    String redirectTo = '/dashboard',
    List<String> except = const [],
    String guard = 'web',
  }) {
    return WebAuthMiddleware.guest(
      redirectTo: redirectTo,
      except: except,
      guard: guard,
    );
  }

  /// Middleware for admin-only routes
  static Middleware admin({
    String redirectTo = '/login',
    List<String> except = const [],
    String guard = 'web',
  }) {
    return WebAuthMiddleware.admin(
      redirectTo: redirectTo,
      except: except,
      guard: guard,
    );
  }

  /// Custom middleware example with role checking
  static Middleware requireRole(String role, {
    String redirectTo = '/dashboard',
  }) {
    return Middleware((Request req, Response res, NextFunction next) async {
      final user = req.authenticatable;
      
      if (user == null) {
        return res.redirect('/login');
      }

      final userData = user.toAuthArray();
      final userRole = userData['role'] as String?;
      
      if (userRole != role) {
        req.session.flash('error', 'Access denied');
        return res.redirect(redirectTo);
      }

      return next();
    });
  }
}

User Model with Authentication

Your user model should implement the Authenticatable interface to work with Khadem's authentication system. This provides methods for authentication and user data management.
dart

import 'package:khadem/khadem.dart';

class User extends KhademModel<User> implements Authenticatable {
  String? name;
  String? email;
  String? password;
  String? role;
  DateTime? emailVerifiedAt;
  DateTime? createdAt;
  DateTime? updatedAt;

  User({
    this.name,
    this.email,
    this.password,
    this.role,
    this.emailVerifiedAt,
    int? id,
  }) {
    this.id = id;
  }

  @override
  List<String> get fillable => [
    'name',
    'email',
    'password',
    'role',
    'email_verified_at',
  ];

  @override
  List<String> get hidden => [
    'password',
  ];

  @override
  Map<String, Type> get casts => {
    'email_verified_at': DateTime,
    'created_at': DateTime,
    'updated_at': DateTime,
  };

  // Authenticatable interface implementation
  @override
  String getAuthIdentifierName() => 'id';

  @override
  dynamic getAuthIdentifier() => id;

  @override
  String? getAuthPassword() => password;

  @override
  Map<String, dynamic> toAuthArray() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'role': role,
      'email_verified_at': emailVerifiedAt?.toIso8601String(),
      'created_at': createdAt?.toIso8601String(),
      'updated_at': updatedAt?.toIso8601String(),
    };
  }

  @override
  User newFactory(Map<String, dynamic> data) {
    return User(
      id: data['id'],
      name: data['name'],
      email: data['email'],
      password: data['password'],
      role: data['role'],
      emailVerifiedAt: data['email_verified_at'] is DateTime
          ? data['email_verified_at']
          : (data['email_verified_at'] is String
              ? DateTime.tryParse(data['email_verified_at'])
              : null),
    );
  }

  @override
  Object? getField(String key) {
    switch (key) {
      case 'id': return id;
      case 'name': return name;
      case 'email': return email;
      case 'password': return password;
      case 'role': return role;
      case 'email_verified_at': return emailVerifiedAt;
      case 'created_at': return createdAt;
      case 'updated_at': return updatedAt;
      default: return null;
    }
  }

  @override
  void setField(String key, dynamic value) {
    switch (key) {
      case 'id': id = value; break;
      case 'name': name = value; break;
      case 'email': email = value; break;
      case 'password': password = value; break;
      case 'role': role = value; break;
      case 'email_verified_at': emailVerifiedAt = value; break;
      case 'created_at': createdAt = value; break;
      case 'updated_at': updatedAt = value; break;
    }
  }

  // Helper methods
  bool get isEmailVerified => emailVerifiedAt != null;

  Future<void> markEmailAsVerified() async {
    emailVerifiedAt = DateTime.now();
    await save();
  }

  bool hasRole(String roleToCheck) => role == roleToCheck;
}

Authentication Middleware

Protect your routes with authentication middleware. The AuthMiddleware automatically validates Bearer tokens and attaches user data to requests.
dart

import 'package:khadem/khadem.dart';

// Use the built-in AuthMiddleware from Khadem
class ApiAuthExample {
  /// Bearer token authentication (default)
  static final bearerAuth = AuthMiddleware.bearer();

  /// Bearer token with role requirement
  static final adminAuth = AuthMiddleware.bearer()
    .withRoles(['admin']);

  /// Bearer token with permissions
  static final userManagement = AuthMiddleware.bearer()
    .withPermissions(['user.create', 'user.update']);

  /// API key authentication
  static final apiKeyAuth = AuthMiddleware.apiKey('X-API-Key');

  /// Basic authentication
  static final basicAuth = AuthMiddleware.basic(realm: 'My API');

  /// Custom authentication
  static final customAuth = AuthMiddleware.custom(
    AuthType.bearer,
    authenticator: (request) async {
      // Custom authentication logic
      final token = request.header('x-custom-token');
      if (token == null) return null;
      
      // Verify token and return user
      final auth = AuthManager();
      return await auth.user(token);
    },
  );
}

// Accessing authenticated user in controller
class UserController {
  static Future<void> getProfile(Request req, Response res) async {
    // Get authenticated user (set by AuthMiddleware)
    final authenticatable = req.authenticatable;
    
    if (authenticatable == null) {
      return res.status(401).sendJson({'error': 'Unauthenticated'});
    }

    final user = authenticatable.toAuthArray();
    
    res.sendJson({
      'message': 'Profile retrieved',
      'data': {'user': user},
    });
  }
}
dart

// routes/web.dart
import 'package:khadem/khadem.dart';

void defineRoutes() {
  // Public routes
  Route.get('/login', WebAuthController.showLogin);
  Route.post('/login', WebAuthController.login);
  Route.get('/register', WebAuthController.showRegister);
  Route.post('/register', WebAuthController.register);

  // API routes with Bearer token authentication
  Route.group(() {
    Route.get('/api/profile', ApiController.profile);
    Route.put('/api/profile', ApiController.updateProfile);
    Route.post('/api/logout', ApiController.logout);
    Route.get('/api/users', ApiController.listUsers);
  }, middleware: [AuthMiddleware.bearer()]);

  // Admin API routes with role requirement
  Route.group(() {
    Route.post('/api/admin/users', AdminController.createUser);
    Route.delete('/api/admin/users/:id', AdminController.deleteUser);
  }, middleware: [
    AuthMiddleware.bearer().withRoles(['admin'])
  ]);

  // Web routes with session-based authentication
  Route.group(() {
    Route.get('/dashboard', WebAuthController.dashboard);
    Route.get('/profile', WebAuthController.profile);
    Route.get('/settings', WebAuthController.settings);
  }, middleware: [WebAuthMiddleware.auth()]);

  // Guest-only routes (redirect if authenticated)
  Route.group(() {
    Route.get('/welcome', WebAuthController.welcome);
  }, middleware: [WebAuthMiddleware.guest()]);
}

Guards and Authentication Drivers

Khadem supports multiple authentication guards and drivers. Guards define authentication contexts, while drivers handle the actual authentication logic.
dart

import 'package:khadem/khadem.dart';

// Using different guards and providers
class MultiGuardController {
  /// Admin login with 'api' guard and 'admins' provider
  static Future<void> adminLogin(Request req, Response res) async {
    final adminAuth = AuthManager(guard: 'api', provider: 'admins');
    final data = await req.validate({
      'email': 'required|email',
      'password': 'required|min:6',
    });

    final authResponse = await adminAuth.attempt(data);

    res.sendJson({
      'message': 'Admin login successful',
      'data': {
        'access_token': authResponse.accessToken,
        'refresh_token': authResponse.refreshToken,
        'token_type': authResponse.tokenType,
        'expires_in': authResponse.expiresIn,
        'user': authResponse.user,
        'guard': 'api',
        'provider': 'admins',
      }
    });
  }

  /// User login with default 'api' guard and 'users' provider
  static Future<void> userLogin(Request req, Response res) async {
    final userAuth = AuthManager(guard: 'api', provider: 'users');
    final data = await req.validate({
      'email': 'required|email',
      'password': 'required|min:6',
    });

    final authResponse = await userAuth.attempt(data);

    res.sendJson({
      'message': 'User login successful',
      'data': authResponse.toMap(),
    });
  }

  /// Web login with 'web' guard
  static Future<void> webLogin(Request req, Response res) async {
    final webAuth = AuthManager(guard: 'web', provider: 'users');
    final data = await req.validate({
      'email': 'required|email',
      'password': 'required|min:6',
    });

    final authResponse = await webAuth.attempt(data);

    // Store in session for web auth
    req.session.set('user_id', authResponse.user['id']);
    req.session.set('access_token', authResponse.accessToken);

    res.redirect('/dashboard');
  }
}
dart

import 'package:khadem/khadem.dart';

class JWTAuthController {
  /// Login with JWT driver (default API guard uses JWT)
  static Future<void> login(Request req, Response res) async {
    final data = await req.validate({
      'email': 'required|email',
      'password': 'required|min:6',
    });

    // AuthManager with 'api' guard uses JWT driver by default
    final auth = AuthManager(guard: 'api');
    final authResponse = await auth.attempt(data);

    res.sendJson({
      'message': 'JWT login successful',
      'data': {
        'access_token': authResponse.accessToken,
        'refresh_token': authResponse.refreshToken,
        'token_type': authResponse.tokenType,
        'expires_in': authResponse.expiresIn,
        'user': authResponse.user,
      }
    });
  }

  /// Refresh access token using refresh token
  static Future<void> refresh(Request req, Response res) async {
    final refreshToken = req.input('refresh_token');

    if (refreshToken == null) {
      return res.status(400).sendJson({'error': 'Refresh token required'});
    }

    final auth = AuthManager(guard: 'api');
    final authResponse = await auth.refresh(refreshToken);

    res.sendJson({
      'message': 'Token refreshed',
      'data': authResponse.toMap(),
    });
  }

  /// Verify JWT token
  static Future<void> verify(Request req, Response res) async {
    final token = req.header('authorization')?.replaceFirst('Bearer ', '');

    if (token == null) {
      return res.status(401).sendJson({'error': 'Token required'});
    }

    final auth = AuthManager(guard: 'api');
    final isValid = await auth.check(token);

    if (!isValid) {
      return res.status(401).sendJson({'error': 'Invalid token'});
    }

    final user = await auth.user(token);

    res.sendJson({
      'message': 'Token is valid',
      'data': {'user': user.toAuthArray()},
    });
  }
}

Token Management

Khadem provides comprehensive token management including refresh tokens, token verification, and secure token generation.
dart

import 'package:khadem/khadem.dart';

class TokenController {
  /// Refresh access token using refresh token
  static Future<void> refreshToken(Request req, Response res) async {
    final refreshToken = req.input('refresh_token');

    if (refreshToken == null || refreshToken.isEmpty) {
      return res.status(400).sendJson({
        'error': 'Refresh token is required'
      });
    }

    final auth = AuthManager();
    final authResponse = await auth.refresh(refreshToken);

    res.sendJson({
      'message': 'Token refreshed successfully',
      'data': {
        'access_token': authResponse.accessToken,
        'refresh_token': authResponse.refreshToken,
        'token_type': authResponse.tokenType,
        'expires_in': authResponse.expiresIn,
        'refresh_expires_in': authResponse.refreshExpiresIn,
      }
    });
  }

  /// Logout and invalidate token
  static Future<void> logout(Request req, Response res) async {
    final token = req.header('authorization')?.replaceFirst('Bearer ', '');

    if (token == null) {
      return res.status(401).sendJson({'error': 'Token required'});
    }

    final auth = AuthManager();
    await auth.logout(token);

    res.sendJson({
      'message': 'Logged out successfully'
    });
  }

  /// Logout from all devices
  static Future<void> logoutAll(Request req, Response res) async {
    final authenticatable = req.authenticatable;
    
    if (authenticatable == null) {
      return res.status(401).sendJson({'error': 'Unauthenticated'});
    }

    final userId = authenticatable.getAuthIdentifier();
    final auth = AuthManager();
    await auth.logoutAll(userId);

    res.sendJson({
      'message': 'Logged out from all devices'
    });
  }

  /// Logout from other devices (keep current session)
  static Future<void> logoutOthers(Request req, Response res) async {
    final authenticatable = req.authenticatable;
    final currentToken = req.header('authorization')?.replaceFirst('Bearer ', '');
    
    if (authenticatable == null || currentToken == null) {
      return res.status(401).sendJson({'error': 'Unauthenticated'});
    }

    final userId = authenticatable.getAuthIdentifier();
    final auth = AuthManager();
    await auth.logoutOthers(userId, currentToken);

    res.sendJson({
      'message': 'Logged out from other devices'
    });
  }
}
dart

import 'package:khadem/khadem.dart';

class AuthVerificationController {
  /// Verify token and return user data
  static Future<void> verifyToken(Request req, Response res) async {
    final token = req.header('authorization')?.replaceFirst('Bearer ', '');

    if (token == null || token.isEmpty) {
      return res.status(401).sendJson({
        'error': 'Token is required'
      });
    }

    try {
      final auth = AuthManager();
      
      // Check if token is valid
      final isValid = await auth.check(token);
      
      if (!isValid) {
        return res.status(401).sendJson({
          'error': 'Invalid or expired token',
          'valid': false,
        });
      }

      // Get user from token
      final user = await auth.user(token);

      res.sendJson({
        'message': 'Token is valid',
        'data': {
          'user': user.toAuthArray(),
          'valid': true,
        }
      });
    } catch (e) {
      res.status(401).sendJson({
        'error': 'Token verification failed',
        'message': e.toString(),
        'valid': false,
      });
    }
  }

  /// Get current authenticated user
  static Future<void> me(Request req, Response res) async {
    // User is already authenticated by AuthMiddleware
    final authenticatable = req.authenticatable;
    
    if (authenticatable == null) {
      return res.status(401).sendJson({'error': 'Unauthenticated'});
    }

    res.sendJson({
      'message': 'Current user',
      'data': {'user': authenticatable.toAuthArray()},
    });
  }
}

Error Handling

Proper error handling is crucial for authentication. Khadem provides specific exceptions and comprehensive error responses.
dart

import 'package:khadem/khadem.dart';

class AuthErrorHandler {
  /// Handle authentication errors with proper HTTP status codes
  static Map<String, dynamic> handleAuthError(dynamic error) {
    if (error is AuthException) {
      return {
        'success': false,
        'error': error.message,
        'code': 'AUTH_ERROR',
        'status_code': error.statusCode,
      };
    }

    // Handle validation errors
    if (error is ValidationException) {
      return {
        'success': false,
        'error': 'Validation failed',
        'errors': error.errors,
        'code': 'VALIDATION_ERROR',
        'status_code': 422,
      };
    }

    // Handle other errors
    return {
      'success': false,
      'error': 'Authentication failed',
      'message': error.toString(),
      'code': 'AUTH_FAILED',
      'status_code': 500,
    };
  }
}

// Usage in controller
class AuthController {
  static Future<void> login(Request req, Response res) async {
    try {
      final data = await req.validate({
        'email': 'required|email',
        'password': 'required|min:6',
      });

      final auth = AuthManager();
      final authResponse = await auth.attempt(data);

      res.sendJson({
        'success': true,
        'message': 'Login successful',
        'data': authResponse.toMap(),
      });
    } on AuthException catch (e) {
      res.status(e.statusCode).sendJson({
        'success': false,
        'error': e.message,
        'code': 'AUTH_ERROR',
      });
    } on ValidationException catch (e) {
      res.status(422).sendJson({
        'success': false,
        'error': 'Validation failed',
        'errors': e.errors,
      });
    } catch (e) {
      res.status(500).sendJson({
        'success': false,
        'error': 'Internal server error',
        'message': e.toString(),
      });
    }
  }

  /// Example with custom error handling
  static Future<void> protectedRoute(Request req, Response res) async {
    try {
      final authenticatable = req.authenticatable;
      
      if (authenticatable == null) {
        throw AuthException('Unauthenticated', statusCode: 401);
      }

      final user = authenticatable.toAuthArray();
      
      // Check permissions
      if (!_hasPermission(user, 'admin')) {
        throw AuthException('Insufficient permissions', statusCode: 403);
      }

      res.sendJson({
        'success': true,
        'message': 'Access granted',
        'data': {'user': user},
      });
    } catch (e) {
      final errorResponse = AuthErrorHandler.handleAuthError(e);
      res.status(errorResponse['status_code']).sendJson(errorResponse);
    }
  }

  static bool _hasPermission(Map<String, dynamic> user, String permission) {
    final permissions = (user['permissions'] as List?)?.cast<String>() ?? [];
    return permissions.contains(permission);
  }
}

On this page