Authorization

Katal provides policy-based authorization through named gates. Policies are evaluated against the current user and the request context.


Defining Policies

Policies are registered on the Application. Each policy is a function that receives the authenticated user and the full MiddlewareContext:

type AuthorizationPolicy = (
  user: User,
  context: MiddlewareContext,
) => boolean | Promise<boolean>;

Single policy

app.definePolicy("admin", (user) => user.role === "admin");

Multiple policies at once

app.definePolicies({
  "admin": (user) => user.role === "admin",
  "editor": (user) => ["admin", "editor"].includes(user.role),
  "owner": (user, ctx) => String(user.id) === ctx.params.id,
  "owner-or-admin": async (user, ctx) => {
    return user.role === "admin" || String(user.id) === ctx.params.id;
  },
});

Protecting Routes

Use the can:<policy-name> middleware key on any route. Always place it after the auth middleware so ctx.user is populated:

router.get("/admin/stats",  AdminController,   { middleware: ["auth", "can:admin"] });
router.put("/posts/:id",    EditPostController, { middleware: ["auth", "can:owner-or-admin"] });
router.delete("/posts/:id", EditPostController, { middleware: ["auth", "can:owner"] });

Error Responses

Scenario Status Body
No authenticated user 401 Problem Details
Policy returns false 403 Problem Details
Policy not registered 500 Problem Details

All errors use application/problem+json:

{
  "type": "https://tools.ietf.org/html/rfc9457",
  "title": "Forbidden",
  "status": 403,
  "detail": "Policy 'admin' denied access."
}

Manual Policy Evaluation

Evaluate a policy programmatically inside a controller:

class DeleteCommentController extends Controller {
  async handle(ctx: RequestContext) {
    const authz = app.resolve<Authorization>("authorization");
    const allowed = await authz.can("owner", ctx as unknown as MiddlewareContext);
    if (!allowed) return this.error("Forbidden", 403);

    await db.deleteComment(ctx.params.id);
    return this.success({ deleted: true });
  }
}

  1. Register auth middleware and protect every restricted route with it.
  2. Define fine-grained policies with definePolicy / definePolicies.
  3. Add can:<policy> entries after auth in each route's middleware array.
  4. Keep policies pure functions — no side effects, easy to unit test.

See Also