AuthRailAuthRail

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.

Permissions Prefetching
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.

Factory Pattern
// 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.

Maintenance Mode Pattern
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.

Hybrid Pattern
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.

Evaluation with Param
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.

On this page