Advanced

Route Guards

Introduction

Les route guards fournissent un middleware de navigation dans Nylo Website. Ils interceptent les transitions de route et vous permettent de controler si un utilisateur peut acceder a une page, de le rediriger ailleurs ou de modifier les donnees transmises a une route.

Les cas d'utilisation courants incluent :

  • Verification d'authentification -- rediriger les utilisateurs non authentifies vers une page de connexion
  • Acces base sur les roles -- restreindre les pages aux utilisateurs administrateurs
  • Validation des donnees -- s'assurer que les donnees requises existent avant la navigation
  • Enrichissement des donnees -- attacher des donnees supplementaires a une route

Les guards sont executes dans l'ordre avant que la navigation ne se produise. Si un guard retourne handled, la navigation s'arrete (soit par redirection, soit par abandon).

Creer un Route Guard

Creez un route guard en utilisant le CLI Metro :

metro make:route_guard auth

Cela genere un fichier guard :

import 'package:nylo_framework/nylo_framework.dart';

class AuthRouteGuard extends NyRouteGuard {
  AuthRouteGuard();

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    // Add your guard logic here
    return next();
  }
}

Cycle de vie du Guard

Chaque route guard possede trois methodes de cycle de vie :

onBefore

Appelee avant que la navigation ne se produise. C'est ici que vous verifiez les conditions et decidez d'autoriser, de rediriger ou d'abandonner la navigation.

@override
Future<GuardResult> onBefore(RouteContext context) async {
  bool isLoggedIn = await Auth.isAuthenticated();

  if (!isLoggedIn) {
    return redirect(HomePage.path);
  }

  return next();
}

Valeurs de retour :

  • next() -- continuer vers le guard suivant ou naviguer vers la route
  • redirect(path) -- rediriger vers une route differente
  • abort() -- annuler completement la navigation

onAfter

Appelee apres une navigation reussie. Utilisez-la pour les analyses, la journalisation ou les effets secondaires post-navigation.

@override
Future<void> onAfter(RouteContext context) async {
  // Log page view
  Analytics.trackPageView(context.routeName);
}

onLeave

Appelee lorsque l'utilisateur quitte une route. Retournez false pour empecher l'utilisateur de partir.

@override
Future<bool> onLeave(RouteContext context) async {
  if (hasUnsavedChanges) {
    // Show confirmation dialog
    return await showConfirmDialog();
  }
  return true; // Allow leaving
}

RouteContext

L'objet RouteContext est transmis a toutes les methodes de cycle de vie du guard et contient des informations sur la navigation :

Propriete Type Description
context BuildContext? Contexte de construction actuel
data dynamic Donnees transmises a la route
queryParameters Map<String, String> Parametres de requete de l'URL
routeName String Nom/chemin de la route cible
originalRouteName String? Nom de route original avant transformations
@override
Future<GuardResult> onBefore(RouteContext context) async {
  // Access route information
  String route = context.routeName;
  dynamic routeData = context.data;
  Map<String, String> params = context.queryParameters;

  return next();
}

Transformer le RouteContext

Creez une copie avec des donnees differentes :

// Change the data type
RouteContext<User> userContext = context.withData<User>(currentUser);

// Copy with modified fields
RouteContext updated = context.copyWith(
  data: enrichedData,
  queryParameters: {"tab": "settings"},
);

Actions du Guard

next

Continuer vers le guard suivant dans la chaine, ou naviguer vers la route si c'est le dernier guard :

return next();

redirect

Rediriger l'utilisateur vers une route differente :

return redirect(LoginPage.path);

Avec des options supplementaires :

return redirect(
  LoginPage.path,
  data: {"returnTo": context.routeName},
  navigationType: NavigationType.pushReplace,
  queryParameters: {"source": "guard"},
);
Parametre Type Defaut Description
path Object requis Chemin de route ou RouteView
data dynamic null Donnees a transmettre a la route de redirection
queryParameters Map<String, dynamic>? null Parametres de requete
navigationType NavigationType pushReplace Methode de navigation
result dynamic null Resultat a retourner
removeUntilPredicate Function? null Predicat de suppression de route
transitionType TransitionType? null Type de transition de page
onPop Function(dynamic)? null Callback au retour

abort

Annuler la navigation sans redirection. L'utilisateur reste sur sa page actuelle :

return abort();

setData

Modifier les donnees qui seront transmises aux guards suivants et a la route cible :

@override
Future<GuardResult> onBefore(RouteContext context) async {
  User user = await fetchUser();

  // Enrich the route data
  setData({"user": user, "originalData": context.data});

  return next();
}

Appliquer des Guards aux routes

Ajoutez des guards a des routes individuelles dans votre fichier de routeur :

appRouter() => nyRoutes((router) {
  router.route(
    HomePage.path,
    (_) => HomePage(),
  ).initialRoute();

  // Add a single guard
  router.route(
    ProfilePage.path,
    (_) => ProfilePage(),
    routeGuards: [AuthRouteGuard()],
  );

  // Add multiple guards (executed in order)
  router.route(
    AdminPage.path,
    (_) => AdminPage(),
    routeGuards: [AuthRouteGuard(), AdminRoleGuard()],
  );
});

Guards de groupe

Appliquez des guards a plusieurs routes simultanement en utilisant les groupes de routes :

appRouter() => nyRoutes((router) {
  router.route(HomePage.path, (_) => HomePage()).initialRoute();

  // All routes in this group require authentication
  router.group(() {
    return {
      'prefix': '/dashboard',
      'route_guards': [AuthRouteGuard()],
    };
  }, (router) {
    router.route(DashboardPage.path, (_) => DashboardPage());
    router.route(SettingsPage.path, (_) => SettingsPage());
    router.route(ProfilePage.path, (_) => ProfilePage());
  });
});

Composition de Guards

Nylo Website fournit des outils pour composer des guards ensemble afin de creer des patrons reutilisables.

GuardStack

Combinez plusieurs guards en un seul guard reutilisable :

final protectedRoute = GuardStack([
  AuthRouteGuard(),
  VerifyEmailGuard(),
  TwoFactorGuard(),
]);

// Use the stack on a route
router.route(
  SecurePage.path,
  (_) => SecurePage(),
  routeGuards: [protectedRoute],
);

GuardStack execute les guards dans l'ordre. Si un guard retourne handled, les guards restants sont ignores.

ConditionalGuard

Appliquer un guard uniquement lorsqu'une condition est vraie :

router.route(
  BetaPage.path,
  (_) => BetaPage(),
  routeGuards: [
    ConditionalGuard(
      condition: (context) => context.queryParameters.containsKey("beta"),
      guard: BetaAccessGuard(),
    ),
  ],
);

Si la condition retourne false, le guard est ignore et la navigation continue.

ParameterizedGuard

Creez des guards qui acceptent des parametres de configuration :

class RoleGuard extends ParameterizedGuard<List<String>> {
  RoleGuard(super.params); // params = allowed roles

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    User? user = await Auth.user<User>();

    if (user == null || !params.contains(user.role)) {
      return redirect(UnauthorizedPage.path);
    }

    return next();
  }
}

// Usage
router.route(
  AdminPage.path,
  (_) => AdminPage(),
  routeGuards: [RoleGuard(["admin", "super_admin"])],
);

Exemples

Guard d'authentification

class AuthRouteGuard extends NyRouteGuard {
  AuthRouteGuard();

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    bool isAuthenticated = await Auth.isAuthenticated();

    if (!isAuthenticated) {
      return redirect(HomePage.path);
    }

    return next();
  }
}

Guard d'abonnement avec parametres

class SubscriptionGuard extends ParameterizedGuard<List<String>> {
  SubscriptionGuard(super.params);

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    User? user = await Auth.user<User>();
    bool hasAccess = params.any((plan) => user?.subscription == plan);

    if (!hasAccess) {
      return redirect(UpgradePage.path, data: {"plans": params});
    }

    setData({"user": user});
    return next();
  }
}

// Require premium or pro subscription
router.route(
  PremiumPage.path,
  (_) => PremiumPage(),
  routeGuards: [
    AuthRouteGuard(),
    SubscriptionGuard(["premium", "pro"]),
  ],
);

Guard de journalisation

class LoggingGuard extends NyRouteGuard {
  LoggingGuard();

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    print("Navigating to: ${context.routeName}");
    return next();
  }

  @override
  Future<void> onAfter(RouteContext context) async {
    print("Arrived at: ${context.routeName}");
  }
}