Advanced Patterns
Practical patterns for composing complex authorization policies.
As your application grows, you'll encounter scenarios that require more than simple role checks. AuthRail's architecture is designed to handle complex, high-scale authorization patterns with ease.
1. Context Enrichment Chains
One of AuthRail's most powerful features is Context Enrichment. Middleware can fetch data and "attach" it to the context, making it available for all subsequent middleware in the rail. This prevents "prop drilling" and redundant database calls.
const attachUserPermissions = async (ctx) => {
if (!ctx.user) return;
// Fetch complex permissions from database or cache
const perms = await db.permissions.findUnique({ where: { userId: ctx.user.id } });
return {
context: { perms }, // This will be shallow-merged into ctx
};
};
export const settingsRail = createRail("settings", [
requireAuth("/login"),
attachUserPermissions, // Now 'perms' is available to all checks below
allowIf((ctx) => ctx.perms.canEditSettings),
allowIf((ctx) => ctx.perms.isAccountOwner),
]);2. Reusable Middleware Factories
Don't repeat logic across different rails. Use factories to generate middleware based on parameters. This keeps your code DRY and maintainable.
// 1. Define the factory
const requirePermission = (permissionPath: string) => {
return (ctx) => {
const hasPerm = get(ctx.perms, permissionPath);
if (!hasPerm) {
return { decision: { type: "deny", message: `Missing: ${permissionPath}` } };
}
};
};
// 2. Use it in multiple rails
export const productRail = createRail("products", [
requireAuth("/login"),
attachUserPermissions,
requirePermission("products.view"),
requirePermission("products.edit"),
]);3. Dynamic Policy Toggling (Feature Gating)
AuthRail makes it trivial to implement global site states or feature flags into your authorization logic.
const blockInMaintenance = (ctx) => {
if (ctx.systemState.isMaintenanceMode && ctx.user.role !== "admin") {
return {
decision: { type: "redirect", to: "/maintenance" }
};
}
};
export const appRail = createRail("main", [
blockInMaintenance,
requireAuth("/login"),
// ... rest of the rail
]);4. Layered Decisions (RBAC + ABAC)
You can combine Role-Based Access Control (RBAC) with Attribute-Based Access Control (ABAC) in a single rail.
export const documentRail = createRail("docs", [
requireAuth("/login"),
// RBAC: Admins always get in
(ctx) => {
if (ctx.user.role === "admin") return { decision: { type: "allow" } };
},
// ABAC: Owners can access their own documents
allowIf((ctx) => ctx.user.id === ctx.document.ownerId),
// ABAC: Collaborators can access if document is public
allowIf((ctx) => ctx.document.isPublic),
]);5. Parameterized Rails
If you need to evaluate a rail based on dynamic parameters (like a specific resource ID), pass them in as part of the context during evaluation.
const result = await documentRail.evaluate({
user,
documentId: params.id, // Parameterized context
systemState: getGlobalState(),
});By mastering these patterns, you can build authorization systems that are not only secure but also highly performant and easy to reason about.