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)
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 { ... };
}