#$secure
#Import
1import { $secure } from "alepha/security";
#Overview
Middleware that enforces authentication and authorization.
Resolves the user from the request context, currentUserAtom, or authorization headers.
Throws UnauthorizedError if no user is resolved, ForbiddenError if checks fail.
Stores the resolved user in currentUserAtom and request.user for downstream access.
Works across all transports (atom-first resolution):
currentUserAtom— set byaction.run()fork, MCP transport, pipelines, jobsrequest.user— set by previous middleware- HTTP headers — JWT/API key resolution
#Check Order
When multiple options are provided, they are checked in this fixed order. All provided options must pass (AND). Each option has its own logic:
- Authentication — Is there a valid user? →
UnauthorizedError(401) - Issuers (OR) — Does the user's realm match at least one? →
ForbiddenError(403) - Roles (OR) — Does the user have at least one of the listed roles? →
ForbiddenError(403) - Permissions (AND) — Does the user's role grant all listed permissions? →
ForbiddenError(403) - Guard — Does the custom function return
true? →ForbiddenError(403)
Permissions declared in $secure() are auto-created in the permission registry at definition time.
Because permissions is an AND list, the resulting ownership is the most
restrictive of the matched grants — one owner-scoped permission narrows the
whole call, whatever order the list was written in.
The resolved user is published to currentUserAtom and to the request
before the guard runs, so anything the guard calls (a repository read in
$owns, for instance) resolves the same tenant the handler would.
#Browser Behavior
On the server, $secure throws UnauthorizedError or ForbiddenError.
In the browser, it returns undefined instead — the handler is never called.
1class OrderController { 2 getOrders = $action({ 3 use: [$secure()], 4 handler: async ({ query }) => { ... }, 5 }); 6 7 deleteOrder = $action({ 8 use: [$secure({ permissions: ["orders:delete"] })], 9 handler: async ({ params }) => { ... },10 });11 12 adminManage = $action({13 use: [$secure({14 issuers: ["main"],15 roles: ["admin"],16 permissions: ["admin:manage"],17 guard: ({ user }) => !!user.email,18 })],19 handler: () => { ... },20 });21}
#Options
| Option | Type | Required | Description |
|---|---|---|---|
issuers |
string[] |
No | Restrict to specific issuers (realms) |
roles |
string[] |
No | Required roles |
permissions |
Object |
No | Required permissions |
guard |
Object |
No | Custom guard |