# Authorization - [Introduction](#introduction) - [Defining Abilities](#defining-abilities) - [Checking Abilities](#checking-abilities) - [Super Admin Bypass](#super-admin-bypass) - [Policies](#policies) - [UI Integration](#ui-integration) - [MagicCan Widget](#magiccan-widget) - [MagicCannot Widget](#magiccannot-widget) - [GateServiceProvider](#gateserviceprovider) - [Generating Policies](#generating-policies) ## Introduction In addition to authentication, Magic provides a simple way to authorize user actions against a given resource. Like Laravel, authorization logic is defined using the `Gate` facade and may be checked anywhere in your application. ```dart // Define abilities (in provider) Gate.define('update-post', (user, post) => user.id == post.userId); // Check abilities (in code) if (Gate.allows('update-post', post)) { showEditButton(); } // Declarative (in UI) MagicCan( ability: 'update-post', arguments: post, child: EditButton(), ) ``` ## Defining Abilities Register abilities using `Gate.define()`. The callback receives the authenticated user as the first argument and optional model/data as the second: ```dart // Simple ability (no model) Gate.define('view-dashboard', (user, _) => user.isActive); // With model Gate.define('update-post', (user, post) => user.id == post.userId); // Complex logic Gate.define('delete-post', (user, post) { return user.isAdmin || user.id == post.userId; }); // Multiple conditions Gate.define('manage-team', (user, team) { return team.ownerId == user.id || team.admins.contains(user.id); }); ``` ## Checking Abilities ### In Controllers ```dart class PostController extends MagicController { Future update(String id, Map data) async { final post = await Post.find(id); if (Gate.denies('update-post', post)) { Magic.error('Forbidden', 'You cannot edit this post.'); return; } await post.fill(data).save(); Magic.success('Success', 'Post updated!'); } } ``` ### Available Methods ```dart // Check if allowed if (Gate.allows('update-post', post)) { // User can update } // Check if denied if (Gate.denies('delete-post', post)) { // User cannot delete } // Alias for allows if (Gate.check('view-admin')) { // Same as allows } ``` ## Super Admin Bypass Use `Gate.before()` to register a global check that runs before all ability checks: ```dart Gate.before((user, ability) { // Super admins bypass all checks if (user.role == 'super_admin') return true; // Continue with normal ability check return null; }); ``` Return values: - `true` → Allow immediately, skip ability check - `false` → Deny immediately, skip ability check - `null` → Continue with normal ability check ## Policies Policies organize related authorization logic into classes. This is the recommended approach for complex applications: ```dart import 'package:magic/magic.dart'; import '../models/post.dart'; class PostPolicy extends Policy { @override void register() { Gate.define('view-post', view); Gate.define('create-post', create); Gate.define('update-post', update); Gate.define('delete-post', delete); } bool view(Authenticatable user, Post post) => post.isPublished || user.id == post.userId; bool create(Authenticatable user, Post? post) => (user as User).isActive; bool update(Authenticatable user, Post post) => user.id == post.userId; bool delete(Authenticatable user, Post post) => (user as User).isAdmin || user.id == post.userId; } ``` ### Registering Policies Register policies in a service provider: ```dart class AppGateServiceProvider extends GateServiceProvider { AppGateServiceProvider(super.app); @override Future boot() async { await super.boot(); // Register policies PostPolicy().register(); CommentPolicy().register(); TeamPolicy().register(); } } ``` ## UI Integration ### MagicCan Widget Conditionally render UI based on authorization: ```dart // Basic usage MagicCan( ability: 'update-post', arguments: post, child: WButton( onTap: () => controller.edit(post), child: WText('Edit Post'), ), ) // With placeholder for denied access MagicCan( ability: 'view-admin-panel', child: AdminPanel(), placeholder: WText('Access Denied', className: 'text-red-500'), ) // Without arguments MagicCan( ability: 'view-dashboard', child: DashboardStats(), ) ``` ### MagicCannot Widget Show content only when user **lacks** an ability: ```dart // Show upgrade prompt to non-premium users MagicCannot( ability: 'access-premium', child: WDiv( className: 'p-4 bg-amber-500/10 rounded-lg', children: [ WText('Upgrade to Premium', className: 'font-bold text-amber-500'), WText('Unlock all features with our premium plan.'), ], ), ) // Show read-only indicator MagicCannot( ability: 'edit-post', arguments: post, child: WText('Read Only', className: 'text-gray-500 italic'), ) ``` ## GateServiceProvider Create a dedicated provider for all authorization logic: ```dart import 'package:magic/magic.dart'; class AppGateServiceProvider extends GateServiceProvider { AppGateServiceProvider(super.app); @override Future boot() async { await super.boot(); // Super admin bypass Gate.before((user, ability) { if ((user as User).role == 'admin') return true; return null; }); // Simple abilities Gate.define('view-dashboard', (user, _) => true); Gate.define('manage-settings', (user, _) => (user as User).isAdmin); // Register policies PostPolicy().register(); TeamPolicy().register(); MonitorPolicy().register(); } } ``` Register in `config/app.dart`: ```dart 'providers': [ (app) => AppGateServiceProvider(app), // ... other providers ], ``` ## Generating Policies Use Magic CLI to generate policy classes: ```bash dart run magic:magic make:policy Post dart run magic:magic make:policy PostPolicy # Explicit naming dart run magic:magic make:policy Comment --model=Comment ``` ### Options | Option | Shortcut | Description | |--------|----------|-------------| | `--model` | `-m` | The model that the policy applies to | **Output:** Creates `lib/app/policies/_policy.dart` with CRUD method stubs. ### Generated Policy Template ```dart import 'package:magic/magic.dart'; import '../models/post.dart'; class PostPolicy extends Policy { @override void register() { Gate.define('view-post', view); Gate.define('create-post', create); Gate.define('update-post', update); Gate.define('delete-post', delete); } bool view(Authenticatable user, Post post) { return true; } bool create(Authenticatable user, Post? post) { return true; } bool update(Authenticatable user, Post post) { return user.id == post.userId; } bool delete(Authenticatable user, Post post) { return user.id == post.userId; } } ``` > [!TIP] > Use policies for model-specific authorization and simple `Gate.define()` calls for general abilities like "view-dashboard" or "access-admin".