Skip to Content
GuidesRuntime validation

Runtime validation

The adapter returns data from a database that Aegis Guard does not control. You can validate that data at runtime with any validation library, or none. A validator is just a function (data: unknown) => T that returns the value or throws.

Register validators

Pass validators to initAegis or createGuard:

import { z } from "zod"; // your dependency, not Aegis Guard's initAegis({ adapter, getSecurityContext, validators: { context: (d) => z.object({ userId: z.string().nullable() }).strict().parse(d), roles: (d) => z.array(z.string()).parse(d), // getUserRoles and getAllowedRoles resource: (d) => z.object({ key: z.string(), description: z.string().optional() }).parse(d), body: (d) => z.object({ resource: z.string().min(1), roles: z.array(z.string()) }).parse(d), }, });

The same shape works with other libraries, with no Aegis Guard dependency on them:

// Valibot roles: (d) => v.parse(v.array(v.string()), d), // ArkType roles: type("string[]").assert, // Hand written roles: (d) => { if (!Array.isArray(d) || d.some((x) => typeof x !== "string")) { throw new TypeError("roles must be string[]"); } return d; },

Fail closed

If a validator throws, the call rejects with AegisValidationError (which extends AegisError, code VALIDATION_FAILED) before any decision is scored. A check can never silently grant access on corrupt data. A denied request still throws AegisDeniedError, so you can tell a real denial (map to 403) apart from corrupt data (map to 500):

import { AegisDeniedError, AegisValidationError } from "aegis-guard/server"; try { await requirePermission("admin.page"); } catch (error) { if (error instanceof AegisDeniedError) { return new Response("Forbidden", { status: 403 }); } if (error instanceof AegisValidationError) { return new Response("Server error", { status: 500 }); } throw error; }

AegisValidationError carries .source (which boundary failed: context, getUserRoles, getAllowedRoles, or getAllResources), .resourceKey when known, and the original library error on .cause.

Notes

  • Validators are opt in. With none configured, behavior is identical to before and adds no overhead: the adapter is passed through by reference.
  • Adapter validators must only assert, never transform. A validator that adds roles or claims would widen access. Use strict schemas for context so unexpected fields are rejected.
  • Validators are synchronous.
Last updated on