Contrôle d'accès par rôle

Un gate (defineGate) déclare qui peut quoi ; l'interface affiche les sections via gate.for(session).can(…) et les loaders re-vérifient côté serveur avec gate.denies(…) puis throw forbidden() — le framework intercepte le throw et rend la réponse 403.

Astuce
Pour les vraies apps, stockez le rôle dans un cookie HMAC signé (ou un JWT) plutôt qu'en clair — un cookie non signé peut être modifié par l'utilisateur dans DevTools. Le loader doit toujours re-vérifier le rôle côté serveur, même si l'UI cache déjà la section.

Session actuelle

user: guest · role: guest

Contenu conditionnel

👋 Public — visible par tous, y compris les invités.
🔒 Espace membres (connectez-vous pour accéder)
⚠ Outils admin (rôle admin requis)
src/app/examples/role-gating/page.tsxTSX
import { defineGate, hasRole, forbidden } from "@bext-stack/framework/authz";

// Declare abilities once. Each receives the user (or null for a guest) plus
// any resource args, so ownership checks live here too. A `before` hook is a
// global short-circuit — the place for a super-admin bypass.
const gate = defineGate<Session>({
  "members.view": (u) => hasRole(u, "user", "admin"),
  "admin.access": (u) => hasRole(u, "admin"),
  "post.update":  (u, post) => hasRole(u, "admin") || post.authorId === u?.id,
}, { before: (u) => hasRole(u, "superadmin") ? true : undefined });

// In a component: show/hide UI.
const acl = gate.for(session);
acl.can("admin.access")   // boolean

// In a loader/action: re-check server-side and 403 if denied.
export async function loader({ request }) {
  const session = readSession(request);
  if (gate.denies(session, "admin.access")) throw forbidden("admins only");
  return { ... };
}