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.
import { requireAuth } from "authrail";
requireAuth("/login"); // Redirect destination if user is missingTechnical Behavior:
- Check: Evaluates if
ctx.useris 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.
import { requireRole } from "authrail";
requireRole("admin"); // Explicit role requiredTechnical Behavior:
- Check: Compares
ctx.user.role === role. - On Failure: Returns
{ type: "deny" }. - Note: This middleware assumes
requireAuthhas already run, ensuringctx.useris defined.
3. allowIf
A flexible predicate-based check. If the predicate returns true, context passes through.
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.
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.
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:
- Deterministic: Avoid using global state. Only use the data provided in the
ctx. - Short-circuiting: To stop the rail, return a
decision. - Enrichment: To add data for later checks, return a
contextobject. - 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.
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.