AuthRailAuthRail

Middleware

Built-in and custom middleware.

Middleware functions are the building blocks of a Rail. They inspect the current context and determine if the evaluation should continue, block, or redirect.

AuthRail provides several highly-optimized built-in middleware utilities to cover common authorization patterns.


1. requireAuth

Ensures that a user object exists within the context. This is typically the first middleware in any protective rail.

Usage
import { requireAuth } from "authrail";

requireAuth("/login"); // Redirect destination if user is missing

Technical Behavior:

  • Check: Evaluates if ctx.user is truthy.
  • On Failure: Returns { type: "redirect", to: "/login" }.
  • On Success: Continues to the next middleware.

2. requireRole

Ensures that the authenticated user possesses a specific role string.

Usage
import { requireRole } from "authrail";

requireRole("admin"); // Explicit role required

Technical Behavior:

  • Check: Compares ctx.user.role === role.
  • On Failure: Returns { type: "deny" }.
  • Note: This middleware assumes requireAuth has already run, ensuring ctx.user is defined.

3. allowIf

A flexible predicate-based check. If the predicate returns true, context passes through.

Usage
import { allowIf } from "authrail";

// Only allow if the account is not suspended
allowIf((ctx) => ctx.user.status === "active");

4. blockIf

The inverse of allowIf. If the predicate returns true, execution is immediately blocked.

Usage
import { blockIf } from "authrail";

// Block if the user is currently on a "Free" plan trying to access "Pro" features
blockIf((ctx) => ctx.user.plan === "free" && ctx.feature.isPremium);

Writing Custom Middleware

While built-ins cover many cases, AuthRail's true power lies in custom middleware. A middleware is a function that receives the current (potentially enriched) context and optionally returns a result.

Custom Middleware Example
const requireSubscription = async (ctx) => {
  // 1. Perform your check (can be async)
  const isSubscribed = await checkBillingStatus(ctx.user.id);

// 2. Return a decision if check fails
if (!isSubscribed) {
return {
decision: { type: "redirect", to: "/billing/upgrade" }
};
}

// 3. (Optional) Enrich context for later middleware
return {
context: { isSubscribed }
};
};

Rules for Custom Middleware:

  1. Deterministic: Avoid using global state. Only use the data provided in the ctx.
  2. Short-circuiting: To stop the rail, return a decision.
  3. Enrichment: To add data for later checks, return a context object.
  4. Implicit Allow: If you return void (or nothing), the rail considers the check "passed" and moves to the next step.

Middleware Composition

Middleware is composed by the order of the array passed to createRail. Evaluation proceeds strictly from index 0 to N.

Composed Rail
createRail("admin-dashboard", [
  requireAuth("/login"),            // Gate 1: Authentication
  requireRole("admin"),             // Gate 2: Authorization
  requireFeature("new-dash"),       // Gate 3: Custom Check
  allowIf((ctx) => !ctx.isLocked),  // Gate 4: Dynamic State
]);

By splitting complex logic into atomic middleware functions, you create a system that is easy to read, test, and debug.

On this page