# Middleware

Middleware lets you inspect and filter navigation requests before they reach a route, making it the right place for authentication guards, permission checks, and pre-navigation side effects.

- [Introduction](#introduction)
- [Generating Middleware](#generating-middleware)
- [Defining Middleware](#defining-middleware)
- [Registering Middleware](#registering-middleware)
    - [Global Middleware](#global-middleware)
    - [Assigning Middleware To Routes](#assigning-middleware-to-routes)
- [Middleware & Responses](#middleware--responses)
- [Authorization Middleware](#authorization-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:

```bash
dart run magic:artisan 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`:

```dart
import 'package:magic/magic.dart';

class EnsureAuthenticated extends MagicMiddleware {
  @override
  String? redirectTarget(String location) {
    if (!Auth.check() && location != '/login') return '/login';
    return null; // Authenticated, or already on /login: allow.
  }
}
```

As you can see, if the user is not authenticated, the middleware returns the login path and the user is redirected to the login screen. If the user is authenticated, it returns `null` and the request proceeds.

### Redirect guards vs blocking guards

Middleware has two hooks, and you pick the one that matches the intent:

- **`redirectTarget(String location)`** for redirect-style guards (auth, guest, role gates). It is evaluated synchronously in the router's `redirect` callback BEFORE the route builds, so the destination view mounts exactly once. Return a path to redirect to, or `null` to allow. Always return `null` when `location` already equals the target, otherwise the redirect loops.
- **`handle(void Function() next)`** for async, non-redirecting concerns (logging, a blocked-state screen). Call `next()` to allow, skip it to block. Both hooks default to allow, so override only the one you need.

> [!IMPORTANT]
> Prefer `redirectTarget` for redirects. Redirecting with an imperative `MagicRoute.to()` from inside `handle()` runs after the route has already mounted, so it remounts the destination view and recreates its state.

### Async Operations

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

```dart
class TeamAccessMiddleware extends MagicMiddleware {
  @override
  Future<void> 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`:

```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:

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

Then use the string key in your route definitions:

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

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

An alias nothing registered stops the app at `Magic.init`, rather than leaving
the route ungated:

```
StateError: Unresolvable route middleware on 1 route:
  /admin: Route middleware alias "admin" is not registered. Register it with
  Kernel.register('admin', () => ...) from a service provider.
```

The router checks every registered route's middleware when it builds, which is
step 7 of `Magic.init`, after every provider has booted. So an alias registered
from `register()` or from `boot()` is equally early, and the usual cause of
this error is a typo or an alias nobody registered at all.

Every registered route includes the ones declared inside a layout. A route in
`MagicRoute.group(layout: ...)` or `MagicRoute.layout(...)` is held in the
layout's own child list rather than the top-level table, and a shell or tab
layout is where a gated screen usually lives, so the check walks both.

Bootstrap rather than navigation is deliberate. `Kernel.resolveAll` throws at
navigation too, but that throw happens inside GoRouter's `redirect` callback,
which routes it to `onException`: the page renders nothing and the log reports
a routing failure, which names the wrong problem.

### Route Group Middleware

You can apply middleware to all routes within a group:

```dart
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:

```dart
@override
Future<void> 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:

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

Use in your routes:

```dart
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:

```dart
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:

```dart
class CanEditPostMiddleware extends MagicMiddleware {
  @override
  Future<void> 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:

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

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

See the [Authorization documentation](https://anilcancakir.com/magic/docs/v0.0/security/authorization.md) for more on defining abilities and policies.
