Authorization

Khadem provides a comprehensive authorization system with role-based access control, permissions, gates, and policies for securing your application resources.

Role-Based Access

Flexible role and permission system

Gates & Policies

Fine-grained authorization control

Middleware Protection

Route-level access control

Gates and Policies

Gates provide simple, closure-based authorization checks while Policies offer organized, class-based authorization for specific models.
dart

// app/providers/auth_service_provider.dart
import 'package:khadem/src/modules/auth/services/gate.dart';

class AuthServiceProvider {
  static void boot() {
    // Define gates for simple authorization checks
    Gate.define('update-post', (user, post) {
      return user['id'] == post['user_id'] || user['role'] == 'admin';
    });

    Gate.define('delete-post', (user, post) {
      return user['role'] == 'admin' ||
             (user['id'] == post['user_id'] &&
              DateTime.parse(post['created_at']).isAfter(
                DateTime.now().subtract(Duration(hours: 24))
              ));
    });

    Gate.define('view-admin-panel', (user) {
      return user['role'] == 'admin' || user['role'] == 'moderator';
    });
  }
}
dart

import 'package:khadem/src/modules/auth/services/gate.dart';

class PostController {
  static Future<Map<String, dynamic>> update(Request request, int postId) async {
    // Get the authenticated user
    final user = request.getAttribute('user') as Map<String, dynamic>;

    // Find the post
    final post = await Post.find(postId);
    if (post == null) {
      return {'error': 'Post not found'};
    }

    // Check authorization using Gate
    if (!Gate.allows('update-post', user, post)) {
      return {
        'error': 'Unauthorized',
        'message': 'You do not have permission to update this post'
      };
    }

    // Update post logic here
    final updatedData = {
      'title': request.input('title'),
      'content': request.input('content'),
      'updated_at': DateTime.now().toIso8601String(),
    };

    await post.update(updatedData);

    return {'success': true, 'post': post};
  }

  static Future<Map<String, dynamic>> delete(Request request, int postId) async {
    final user = request.getAttribute('user') as Map<String, dynamic>;
    final post = await Post.find(postId);

    if (post == null) {
      return {'error': 'Post not found'};
    }

    if (!Gate.allows('delete-post', user, post)) {
      return {'error': 'Unauthorized to delete this post'};
    }

    await post.delete();
    return {'success': true, 'message': 'Post deleted'};
  }
}

Authorization Policies

Policies provide a clean, organized way to handle authorization logic for specific models. Each policy method corresponds to an action that can be performed on the model.
dart

import 'package:khadem/src/modules/auth/policies/policy.dart';

class PostPolicy extends Policy {
  @override
  bool viewAny(Map<String, dynamic> user) {
    return true; // Anyone can view posts
  }

  @override
  bool view(Map<String, dynamic> user, Map<String, dynamic> post) {
    return true; // Anyone can view a specific post
  }

  @override
  bool create(Map<String, dynamic> user) {
    return user.isNotEmpty; // Only authenticated users can create
  }

  @override
  bool update(Map<String, dynamic> user, Map<String, dynamic> post) {
    return user['id'] == post['user_id'] || user['role'] == 'admin';
  }

  @override
  bool delete(Map<String, dynamic> user, Map<String, dynamic> post) {
    return user['role'] == 'admin' ||
           (user['id'] == post['user_id'] &&
            DateTime.parse(post['created_at']).isAfter(
              DateTime.now().subtract(Duration(hours: 24))
            ));
  }

  // Custom policy methods
  bool publish(Map<String, dynamic> user, Map<String, dynamic> post) {
    return user['role'] == 'admin' || user['role'] == 'editor';
  }

  bool feature(Map<String, dynamic> user, Map<String, dynamic> post) {
    return user['role'] == 'admin';
  }
}
dart

// app/providers/auth_service_provider.dart
import 'package:khadem/src/modules/auth/services/gate.dart';
import '../policies/post_policy.dart';
import '../policies/user_policy.dart';
import '../policies/comment_policy.dart';

class AuthServiceProvider {
  static void boot() {
    // Register policies for different models
    Gate.policy('Post', PostPolicy());
    Gate.policy('User', UserPolicy());
    Gate.policy('Comment', CommentPolicy());

    // You can also register policies with custom keys
    Gate.policy('posts', PostPolicy());
    Gate.policy('users', UserPolicy());
  }
}

Roles and Permissions

Implement role-based access control with a flexible permission system. Users can have multiple roles, and roles can have multiple permissions.
dart

import 'package:khadem/src/core/database/model_base/khadem_model.dart';

class Role extends KhademModel<Role> {
  String? name;
  String? description;
  DateTime? createdAt;
  DateTime? updatedAt;

  Role({
    this.name,
    this.description,
    int? id,
  }) {
    this.id = id;
  }

  @override
  List<String> get fillable => [
    'name',
    'description',
  ];

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

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

  @override
  Map<String, dynamic> get relationships => {
    'users': {
      'type': 'belongsToMany',
      'model': 'User',
      'pivot_table': 'user_roles',
      'foreign_key': 'role_id',
      'related_key': 'user_id',
    },
    'permissions': {
      'type': 'belongsToMany',
      'model': 'Permission',
      'pivot_table': 'role_permissions',
      'foreign_key': 'role_id',
      'related_key': 'permission_id',
    },
  };

  @override
  Role newFactory(Map<String, dynamic> data) {
    return Role(
      id: data['id'],
      name: data['name'],
      description: data['description'],
    );
  }

  @override
  Object? getField(String key) {
    switch (key) {
      case 'id': return id;
      case 'name': return name;
      case 'description': return description;
      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 'description': description = value; break;
      case 'created_at': createdAt = value; break;
      case 'updated_at': updatedAt = value; break;
    }
  }
}
dart

import 'package:khadem/src/core/database/model_base/khadem_model.dart';

class Permission extends KhademModel<Permission> {
  String? name;
  String? description;
  DateTime? createdAt;
  DateTime? updatedAt;

  Permission({
    this.name,
    this.description,
    int? id,
  }) {
    this.id = id;
  }

  @override
  List<String> get fillable => [
    'name',
    'description',
  ];

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

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

  @override
  Map<String, dynamic> get relationships => {
    'roles': {
      'type': 'belongsToMany',
      'model': 'Role',
      'pivot_table': 'role_permissions',
      'foreign_key': 'permission_id',
      'related_key': 'role_id',
    },
  };

  @override
  Permission newFactory(Map<String, dynamic> data) {
    return Permission(
      id: data['id'],
      name: data['name'],
      description: data['description'],
    );
  }

  @override
  Object? getField(String key) {
    switch (key) {
      case 'id': return id;
      case 'name': return name;
      case 'description': return description;
      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 'description': description = value; break;
      case 'created_at': createdAt = value; break;
      case 'updated_at': updatedAt = value; break;
    }
  }
}
dart

import 'package:khadem/src/core/database/model_base/khadem_model.dart';
import 'package:khadem/src/modules/auth/contracts/authenticatable.dart';

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

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

  @override
  List<String> get fillable => [
    'name',
    'email',
    'password',
    '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,
  };

  @override
  Map<String, dynamic> get relationships => {
    'roles': {
      'type': 'belongsToMany',
      'model': 'Role',
      'pivot_table': 'user_roles',
      'foreign_key': 'user_id',
      'related_key': 'role_id',
    },
    'permissions': {
      'type': 'belongsToMany',
      'model': 'Permission',
      'pivot_table': 'user_permissions',
      'foreign_key': 'user_id',
      'related_key': 'permission_id',
    },
  };

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

  @override
  dynamic getAuthIdentifier() => id;

  @override
  String getAuthPassword() => password ?? '';

  @override
  bool getRememberToken() => false;

  @override
  String? getRememberTokenName() => null;

  @override
  User newFactory(Map<String, dynamic> data) {
    return User(
      id: data['id'],
      name: data['name'],
      email: data['email'],
      password: data['password'],
      emailVerifiedAt: data['email_verified_at'],
    );
  }

  @override
  Object? getField(String key) {
    switch (key) {
      case 'id': return id;
      case 'name': return name;
      case 'email': return email;
      case 'password': return password;
      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 'email_verified_at': emailVerifiedAt = value; break;
      case 'created_at': createdAt = value; break;
      case 'updated_at': updatedAt = value; break;
    }
  }

  // Authorization helper methods
  Future<bool> hasRole(String roleName) async {
    final roles = await this.roles;
    return roles.any((role) => role.name == roleName);
  }

  Future<bool> hasPermission(String permissionName) async {
    final permissions = await this.permissions;
    return permissions.any((permission) => permission.name == permissionName);
  }

  Future<bool> hasAnyRole(List<String> roleNames) async {
    for (final roleName in roleNames) {
      if (await hasRole(roleName)) return true;
    }
    return false;
  }

  Future<bool> hasAllRoles(List<String> roleNames) async {
    for (final roleName in roleNames) {
      if (!await hasRole(roleName)) return false;
    }
    return true;
  }

  Future<bool> hasAnyPermission(List<String> permissionNames) async {
    for (final permissionName in permissionNames) {
      if (await hasPermission(permissionName)) return true;
    }
    return false;
  }
}

Authorization Middleware

Use middleware to automatically check roles and permissions on protected routes. This provides route-level access control without cluttering your controllers.
dart

import 'package:khadem/src/core/http/middleware.dart';
import 'package:khadem/src/core/http/request.dart';
import 'package:khadem/src/core/http/response.dart';

class RoleMiddleware extends Middleware {
  final List<String> roles;

  RoleMiddleware(this.roles);

  @override
  Future<Response> handle(Request request, Function next) async {
    final user = request.getAttribute('user') as Map<String, dynamic>?;

    if (user == null) {
      return Response.unauthorized('Authentication required');
    }

    // Check if user has any of the required roles
    final userRoles = user['roles'] as List<dynamic>? ?? [];
    final hasRequiredRole = roles.any((role) =>
      userRoles.any((userRole) => userRole['name'] == role)
    );

    if (!hasRequiredRole) {
      return Response.forbidden('Insufficient permissions');
    }

    return await next(request);
  }
}
dart

import 'package:khadem/src/core/http/middleware.dart';
import 'package:khadem/src/core/http/request.dart';
import 'package:khadem/src/core/http/response.dart';

class PermissionMiddleware extends Middleware {
  final List<String> permissions;

  PermissionMiddleware(this.permissions);

  @override
  Future<Response> handle(Request request, Function next) async {
    final user = request.getAttribute('user') as Map<String, dynamic>?;

    if (user == null) {
      return Response.unauthorized('Authentication required');
    }

    // Check if user has any of the required permissions
    final userPermissions = user['permissions'] as List<dynamic>? ?? [];
    final hasRequiredPermission = permissions.any((permission) =>
      userPermissions.any((userPermission) => userPermission['name'] == permission)
    );

    if (!hasRequiredPermission) {
      return Response.forbidden('Insufficient permissions');
    }

    return await next(request);
  }
}
dart

// routes/web.dart
import 'package:khadem/src/core/http/route.dart';
import 'package:khadem/src/modules/auth/middlewares/auth_middleware.dart';
import '../controllers/admin_controller.dart';
import '../controllers/user_controller.dart';
import '../controllers/editor_controller.dart';

// Admin routes - require admin role
Route.group(() {
  Route.get('/admin/dashboard', AdminController.dashboard);
  Route.get('/admin/users', AdminController.users);
  Route.post('/admin/users/{id}/ban', AdminController.banUser);
}, middleware: [AuthMiddleware(), RoleMiddleware(['admin'])]);

// Moderator routes - require admin or moderator role
Route.group(() {
  Route.get('/admin/moderation', AdminController.moderation);
  Route.post('/posts/{id}/moderate', AdminController.moderatePost);
}, middleware: [AuthMiddleware(), RoleMiddleware(['admin', 'moderator'])]);

// Editor routes - require editor permission
Route.group(() {
  Route.get('/editor/dashboard', EditorController.dashboard);
  Route.post('/posts/{id}/publish', EditorController.publishPost);
}, middleware: [AuthMiddleware(), PermissionMiddleware(['publish_posts'])]);

// User management - require specific permission
Route.group(() {
  Route.get('/users', UserController.index);
  Route.put('/users/{id}', UserController.update);
}, middleware: [AuthMiddleware(), PermissionMiddleware(['manage_users'])]);

Authorization Helper Methods

Convenient methods for checking roles and permissions throughout your application. These methods can be used in controllers, services, and other parts of your application.
dart

import 'package:khadem/src/modules/auth/services/gate.dart';

class AuthHelpers {
  static Future<bool> canUser(Map<String, dynamic> user, String ability, [dynamic model]) async {
    return Gate.allows(ability, user, model);
  }

  static Future<bool> cannotUser(Map<String, dynamic> user, String ability, [dynamic model]) async {
    return Gate.denies(ability, user, model);
  }

  static Future<bool> hasRole(Map<String, dynamic> user, String roleName) async {
    final roles = user['roles'] as List<dynamic>? ?? [];
    return roles.any((role) => role['name'] == roleName);
  }

  static Future<bool> hasPermission(Map<String, dynamic> user, String permissionName) async {
    final permissions = user['permissions'] as List<dynamic>? ?? [];
    return permissions.any((permission) => permission['name'] == permissionName);
  }

  static Future<bool> hasAnyRole(Map<String, dynamic> user, List<String> roleNames) async {
    for (final roleName in roleNames) {
      if (await hasRole(user, roleName)) return true;
    }
    return false;
  }

  static Future<bool> hasAllPermissions(Map<String, dynamic> user, List<String> permissionNames) async {
    for (final permissionName in permissionNames) {
      if (!await hasPermission(user, permissionName)) return false;
    }
    return true;
  }
}
dart

import 'package:khadem/src/modules/auth/services/gate.dart';

class PostController {
  static Future<Map<String, dynamic>> update(Request request, int postId) async {
    final user = request.getAttribute('user') as Map<String, dynamic>;
    final post = await Post.find(postId);

    if (post == null) {
      return {'error': 'Post not found'};
    }

    // Check using Gate
    if (!Gate.allows('update-post', user, post)) {
      return {'error': 'Unauthorized'};
    }

    // Or check using policy
    final postPolicy = PostPolicy();
    if (!postPolicy.update(user, post)) {
      return {'error': 'Unauthorized'};
    }

    // Update logic here
    final updatedData = {
      'title': request.input('title'),
      'content': request.input('content'),
    };

    await post.update(updatedData);
    return {'success': true, 'post': post};
  }

  static Future<Map<String, dynamic>> publish(Request request, int postId) async {
    final user = request.getAttribute('user') as Map<String, dynamic>;
    final post = await Post.find(postId);

    if (post == null) {
      return {'error': 'Post not found'};
    }

    final postPolicy = PostPolicy();

    // Check if user can publish
    if (!postPolicy.publish(user, post)) {
      return {'error': 'Unauthorized to publish posts'};
    }

    await post.update({'published': true, 'published_at': DateTime.now()});
    return {'success': true, 'message': 'Post published'};
  }
}

Template Authorization

Use authorization directives in your templates to conditionally render content based on user roles and permissions.
dart

// In your template rendering logic
class TemplateHelpers {
  static String renderAuthorizedContent(Map<String, dynamic> user, String template) {
    final buffer = StringBuffer();

    // Simple template authorization example
    if (user.isNotEmpty) {
      buffer.write('<h1>Welcome back, ${user['name']}!</h1>');

      // Check permissions for admin content
      if (_hasPermission(user, 'manage_users')) {
        buffer.write('<a href="/admin/users">Manage Users</a>');
      }

      // Check roles for editor content
      if (_hasRole(user, 'admin') || _hasRole(user, 'editor')) {
        buffer.write('<a href="/editor">Editor Panel</a>');
      }
    } else {
      buffer.write('<a href="/login">Login</a>');
      buffer.write('<a href="/register">Register</a>');
    }

    return buffer.toString();
  }

  static bool _hasRole(Map<String, dynamic> user, String roleName) {
    final roles = user['roles'] as List<dynamic>? ?? [];
    return roles.any((role) => role['name'] == roleName);
  }

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

On this page