search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

Middleware

Introduction

Middleware provide a convenient mechanism for inspecting and filtering navigation requests entering your application. For example, Magic includes a middleware that verifies the user is authenticated. If the user is not authenticated, the middleware will redirect the user to your application's login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.

Of course, additional middleware can be written to perform a variety of tasks besides authentication. Middleware are stored in the lib/app/middleware directory.

Generating Middleware

Use the Magic CLI to generate a new middleware class:

dart run magic:magic make:middleware Auth

This creates lib/app/middleware/auth.dart with a MagicMiddleware stub ready to implement.

Defining Middleware

To create a new middleware, create a class that extends MagicMiddleware:

import 'package:magic/magic.dart';

class EnsureAuthenticated extends MagicMiddleware {
  @override
  Future handle(void Function() next) async {
    if (Auth.check()) {
      next(); // Allow navigation to proceed
    } else {
      MagicRoute.to('/login'); // Redirect to login
    }
  }
}

As you can see, if the user is not authenticated, the middleware will redirect the user to the login screen. If the user is authenticated, the request is passed further into the application by calling next().

[!IMPORTANT] If you do NOT call next(), navigation will be blocked. Always ensure you either call next() or redirect the user.

Async Operations

All middleware is asynchronous. You may perform await operations before deciding whether to call next():

class TeamAccessMiddleware extends MagicMiddleware {
  @override
  Future handle(void Function() next) async {
    final team = await Team.find(Request.route('teamId'));
    
    if (team != null && team.hasMember(Auth.id())) {
      next();
    } else {
      MagicRoute.to('/unauthorized');
    }
  }
}

Registering Middleware

Global Middleware

If you want middleware to run during every navigation in your application, register it globally in your lib/app/kernel.dart:

void registerKernel() {
  Kernel.global([
    () => LoggingMiddleware(),
    () => VerifyDeviceMiddleware(),
  ]);
}

Global middleware runs on every route, in the order they are registered.

Assigning Middleware To Routes

To assign middleware to specific routes, first register it with a key in your Kernel:

void registerKernel() {
  Kernel.registerAll({
    'auth': () => EnsureAuthenticated(),
    'guest': () => RedirectIfAuthenticated(),
    'admin': () => EnsureAdmin(),
  });
}

Then use the string key in your route definitions:

// Single middleware
MagicRoute.page('/dashboard', () => DashboardView())
    .middleware(['auth']);

// Multiple middleware (executed in order)
MagicRoute.page('/admin', () => AdminPanel())
    .middleware(['auth', 'admin']);

Route Group Middleware

You can apply middleware to all routes within a group:

MagicRoute.group(
  middleware: ['auth'],
  routes: () {
    MagicRoute.page('/dashboard', () => DashboardView());
    MagicRoute.page('/profile', () => ProfileView());
    MagicRoute.page('/settings', () => SettingsView());
  },
);

Middleware & Responses

Since middleware controls navigation flow, there are two possible outcomes:

Action Effect
Call next() Navigation proceeds to the route
Don't call next() Navigation is blocked

When blocking, you should redirect the user:

@override
Future handle(void Function() next) async {
  if (await hasValidSubscription()) {
    next();
  } else {
    // Don't call next() - redirect instead
    MagicRoute.to('/subscription/required');
    Magic.error('Subscription Required', 'Please upgrade your plan.');
  }
}

Authorization Middleware

Magic includes AuthorizeMiddleware that integrates with the Gate authorization system—equivalent to Laravel's can middleware.

Basic Usage

Register authorization middleware in your Kernel:

void registerKernel() {
  Kernel.registerAll({
    'auth': () => EnsureAuthenticated(),
    'can:manage-team': () => AuthorizeMiddleware('manage-team'),
    'can:admin': () => AuthorizeMiddleware('admin-access'),
  });
}

Use in your routes:

MagicRoute.page('/team/settings', () => TeamSettings())
    .middleware(['auth', 'can:manage-team']);

MagicRoute.page('/admin', () => AdminPanel())
    .middleware(['auth', 'can:admin']);

Custom Redirect

By default, unauthorized users are redirected to /unauthorized. Customize this:

Kernel.register('can:admin', () => AuthorizeMiddleware(
  'admin-access',
  unauthorizedRoute: '/access-denied',
));

With Model Arguments

For authorization requiring a model (like checking ownership), create a custom middleware:

class CanEditPostMiddleware extends MagicMiddleware {
  @override
  Future handle(void Function() next) async {
    final postId = Request.route('id');
    final post = await Post.find(postId);

    if (post != null && Gate.allows('edit-post', post)) {
      next();
    } else {
      MagicRoute.to('/unauthorized');
    }
  }
}

Register and use it:

Kernel.register('can:edit-post', () => CanEditPostMiddleware());

MagicRoute.page('/posts/:id/edit', (id) => EditPostView(id: id))
    .middleware(['auth', 'can:edit-post']);

See the Authorization documentation for more on defining abilities and policies.