go_router_guards 1.0.0+1 copy "go_router_guards: ^1.0.0+1" to clipboard
go_router_guards: ^1.0.0+1 copied to clipboard

unlisted

A flexible and extensible guard system for Go Router that allows you to chain multiple guards together for route protection.

go_router_guards #

A flexible and extensible guard system for Go Router that enables type-safe route protection with complex boolean logic support.

ci coverage pub package style: very good analysis License: MIT

Quick Start #

Installation #

Add go_router_guards to your pubspec.yaml:

dependencies:
  go_router_guards: ^1.0.0+1

Type-Safe Routes with Guard Expressions #

Following VGV's routing best practices, use type-safe routes with guard expressions:

import 'package:go_router_guards/go_router_guards.dart';

// Define type-safe routes
@TypedGoRoute<LoginRoute>(path: '/login')
class LoginRoute extends GoRouteData {
  const LoginRoute();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const LoginScreen();
  }
}

@TypedGoRoute<ProtectedRoute>(path: '/protected')
class ProtectedRoute extends GoRouteData with GuardedRoute {
  const ProtectedRoute();

  @override
  GuardExpression get guards => Guards.all([
    Guards.guard(AuthenticationGuard()),
    Guards.guard(RoleGuard(['admin'])),
  ]);

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const ProtectedScreen();
  }
}

// Create type-safe guards
class AuthenticationGuard implements RouteGuard {
  @override
  FutureOr<String?> redirect(BuildContext context, GoRouterState state) async {
    final authState = context.read<AuthCubit>().state;
    if (!authState.isAuthenticated) {
      return LoginRoute().location; // Type-safe navigation
    }
    return null;
  }
}

// Navigate using type-safe routes
ElevatedButton(
  onPressed: () => ProtectedRoute().go(context),
  child: const Text('Go to Protected Route'),
)

Core Features #

RouteGuard Interface #

Implement guards to protect your routes:

mixin RouteGuard {
  FutureOr<String?> redirect(BuildContext context, GoRouterState state);
}
  • Return null to allow access
  • Return a route location (e.g., LoginRoute().location) to redirect

Guard Expressions with Logical Operators #

Create complex guard logic using boolean expressions:

// Simple AND: both must pass
Guards.all([
  Guards.guard(AuthenticationGuard()),
  Guards.guard(RoleGuard(['admin'])),
])

// Simple OR: either can pass
Guards.anyOf([
  Guards.guard(AuthenticationGuard()),
  Guards.guard(AdminGuard()),
])

// Complex expression: (a & b) || c
Guards.anyOf([
  Guards.all([
    Guards.guard(AuthenticationGuard()),
    Guards.guard(RoleGuard(['admin'])),
  ]),
  Guards.guard(SuperAdminGuard()),
])

// Multiple guards with ALL: all must pass
Guards.all([
  Guards.guard(AuthenticationGuard()),
  Guards.guard(RoleGuard(['admin'])),
  Guards.guard(SubscriptionGuard()),
  Guards.guard(PaymentGuard()),
])

// Multiple guards with ANY OF: any can pass
Guards.anyOf([
  Guards.guard(AuthenticationGuard()),
  Guards.guard(AdminGuard()),
  Guards.guard(SuperAdminGuard()),
])

// Multiple guards with ONE OF: exactly one must pass
Guards.oneOf([
  Guards.guard(AuthenticationGuard()),
  Guards.guard(AdminGuard()),
  Guards.guard(SuperAdminGuard()),
], '/unauthorized')

// ONE OF: exactly one must pass
Guards.oneOf([
  Guards.guard(AuthenticationGuard()),
  Guards.guard(AdminGuard()),
], '/unauthorized')

GuardedRoute Mixin #

Add guard functionality to your route classes:

class AdminRoute extends GoRouteData with GuardedRoute {
  const AdminRoute();

  @override
  GuardExpression get guards => Guards.anyOf([
    Guards.all([
      Guards.guard(AuthenticationGuard()),
      Guards.guard(RoleGuard(['admin'])),
    ]),
    Guards.guard(SuperAdminGuard()),
  ]);

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const AdminScreen();
  }
}

Complex Guard Logic #

// Route accessible by:
// - Authenticated users with admin role AND premium subscription
// - OR super admins
// - OR users with special access token
class PremiumAdminRoute extends GoRouteData with GuardedRoute {
  const PremiumAdminRoute();

  @override
  GuardExpression get guards => Guards.anyOf([
    Guards.all([
      Guards.guard(AuthenticationGuard()),
      Guards.guard(RoleGuard(['admin'])),
      Guards.guard(SubscriptionGuard()),
    ]),
    Guards.anyOf([
      Guards.guard(SuperAdminGuard()),
      Guards.guard(SpecialAccessGuard()),
    ]),
  ]);

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const PremiumAdminScreen();
  }
}

Conditional Guards #

class ConditionalGuard implements RouteGuard {
  @override
  FutureOr<String?> redirect(BuildContext context, GoRouterState state) async {
    final appState = context.read<AppCubit>().state;
    
    if (appState.isMaintenanceMode) {
      return MaintenanceRoute().location;
    }
    
    if (appState.isOffline) {
      return OfflineRoute().location;
    }
    
    return null;
  }
}

Testing Guards #

test('complex guard expression', () async {
  final expression = Guards.anyOf([
    Guards.all([
      Guards.guard(AuthenticationGuard()),
      Guards.guard(RoleGuard(['admin'])),
    ]),
    Guards.guard(SuperAdminGuard()),
  ]);

  // Test with authenticated admin
  when(mockAuthCubit.state).thenReturn(AuthenticatedState());
  when(mockUserCubit.state).thenReturn(UserState(roles: ['admin']));
  
  final result = await expression.execute(mockContext, mockState);
  expect(result, isNull); // Access granted
});

Best Practices #

1. Use Type-Safe Navigation #

Always use type-safe routes for navigation:

// ✅ Good - Type-safe
context.go(ProtectedRoute().location);
ProtectedRoute().go(context);

// ❌ Bad - Hardcoded paths
context.go('/protected');

2. Order Guards by Performance #

Order guards from fastest to slowest in ALL expressions:

Guards.all([
  Guards.guard(AppInitializationGuard()), // Fast check
  Guards.guard(AuthenticationGuard()),    // Medium check
  Guards.guard(AsyncGuard()),             // Slow async check
])

3. Create Reusable Guard Expressions #

Extract common guard logic:

class PremiumFeatureGuard implements RouteGuard {
  @override
  FutureOr<String?> redirect(BuildContext context, GoRouterState state) async {
    final userState = context.read<UserCubit>().state;
    if (!userState.hasPremiumAccess) {
      return UpgradeRoute().location;
    }
    return null;
  }
}

// Reusable expression
final premiumGuard = Guards.guard(PremiumFeatureGuard());
final adminGuard = Guards.guard(RoleGuard(['admin']));

// Use in multiple routes
final adminPremiumGuard = Guards.all([adminGuard, premiumGuard]);

4. Handle Guard Failures Gracefully #

class RobustGuard implements RouteGuard {
  @override
  FutureOr<String?> redirect(BuildContext context, GoRouterState state) async {
    try {
      final userState = context.read<UserCubit>().state;
      if (!userState.isAuthenticated) {
        return LoginRoute().location;
      }
      return null;
    } catch (e) {
      return ErrorRoute().location;
    }
  }
}

Testing #

Unit Testing Guard Expressions #

test('AND expression with both guards passing', () async {
  final expression = Guards.all([
    Guards.guard(AuthenticationGuard()),
    Guards.guard(RoleGuard(['admin'])),
  ]);
  
  when(mockAuthCubit.state).thenReturn(AuthenticatedState());
  when(mockUserCubit.state).thenReturn(UserState(roles: ['admin']));
  
  final result = await expression.execute(mockContext, mockState);
  expect(result, isNull);
});

Integration Testing #

testWidgets('complex guard expression redirects correctly', (tester) async {
  await tester.pumpWidget(MyApp());
  
  await tester.tap(find.text('Premium Admin Route'));
  await tester.pumpAndSettle();
  
  // Should redirect to login if not authenticated
  expect(find.text('Login'), findsOneWidget);
});

Contributing #

See CONTRIBUTING.md for details.

License #

This project is licensed under the MIT License - see the LICENSE file for details.

4
likes
0
points
2
downloads

Publisher

verified publisheraquiles.dev

Weekly Downloads

A flexible and extensible guard system for Go Router that allows you to chain multiple guards together for route protection.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, go_router, meta

More

Packages that depend on go_router_guards