LogoRevali

Guards

Allow or deny a request before the endpoint runs - authentication, roles, permissions.

A guard decides whether a request may reach the endpoint. Use one for authentication, role checks, and permissions. Guards run after all middleware, so they can read anything middleware stored in Data. The example below uses the LoadUser middleware from Writing a LifecycleComponent.

Example#

lib/components/require_role.dart
import 'package:revali_router/revali_router.dart';

class RequireRole implements LifecycleComponent {
  const RequireRole(this.role);

  final String role;

  GuardResult check(Data data) {
    final user = data.get<User>();

    if (user == null) {
      return const GuardResult.block(statusCode: 401, body: 'Sign in first');
    }

    if (user.role != role) {
      return GuardResult.block(body: 'Requires the $role role');
    }

    return const GuardResult.pass();
  }
}
routes/controllers/admin_controller.dart
import 'package:revali_router/revali_router.dart';

@LifecycleComponents([LoadUser]) // middleware that stores the User in Data
@RequireRole('admin')
@Controller('admin')
class AdminController {
  const AdminController();

  @Get('stats')
  String stats() => 'ok';
}
CallerResponse
No user loaded401 Sign in first
A user without the admin role403 Requires the admin role
An admin200 {"data":"ok"}

In debug mode, the blocked responses also have a __DEBUG__ block appended.

Results#

ResultEffect
GuardResult.pass() Continue to the next guard, then to the interceptors and the endpoint.
GuardResult.block({statusCode, headers, body}) End the request. The status defaults to 403.

The method can be async and return Future<GuardResult>. block takes the same arguments as the other error results. See Error Responses.

You can also throw an exception from a guard and map it to a response with an exception catcher. That keeps the error format in one place when several guards fail the same way.

Classic Style#

As an alternative, implement Guard and its protect method:

lib/components/admin_guard.dart
import 'package:revali_router/revali_router.dart';

class AdminGuard implements Guard {
  const AdminGuard();

  @override
  Future<GuardResult> protect(Context context) async {
    final user = context.data.get<User>();

    return user?.role == 'admin'
        ? const GuardResult.pass()
        : const GuardResult.block();
  }
}

Apply it with @AdminGuard(), or by type with @Guards([AdminGuard]).