#Authentication
Alepha provides JWT-based authentication through $issuer for token management and $realm for full user management.
#Token Management with $issuer
$issuer is the low-level primitive for creating and verifying JWT tokens. Use it when you manage users yourself or integrate with an external identity provider.
1import { $issuer } from "alepha/security"; 2import { $action } from "alepha/server"; 3import { t } from "alepha"; 4 5class AuthController { 6 issuer = $issuer({ 7 secret: "your-secret-key", 8 }); 9 10 login = $action({11 method: "POST",12 path: "/auth/login",13 schema: {14 body: t.object({15 email: t.email(),16 password: t.text(),17 }),18 },19 handler: async ({ body }) => {20 const user = await this.authenticate(body.email, body.password);21 return this.issuer.createToken(user);22 },23 });24}
#Internal vs External Issuers
An internal issuer signs and verifies tokens with a shared secret:
1issuer = $issuer({2 secret: "my-secret",3});
An external issuer verifies tokens from an external provider (Auth0, Keycloak, etc.) using JWKS:
1issuer = $issuer({2 jwks: () => process.env.AUTH0_JWKS_URL,3 profile: (payload) => ({4 id: payload.sub,5 email: payload.email,6 name: payload.name,7 }),8});
#Token Lifecycle
$issuer manages access tokens and refresh tokens:
| Setting | Default |
|---|---|
| Access token expiration | 15 minutes |
| Refresh token expiration | 30 days |
Override via the settings option:
1issuer = $issuer({2 secret: "...",3 settings: {4 accessToken: { expiration: [1, "hours"] },5 refreshToken: { expiration: [90, "days"] },6 },7});
#User Management with $realm
$realm is a higher-level primitive that wraps $issuer with built-in user management: registration, login, sessions, password handling, and identity providers.
1import { $realm } from "alepha/api/users";2 3class App {4 realm = $realm();5}
$realm ships with two default roles:
- admin -- Full access to all resources and permissions.
- user -- Access to owned resources only.
#Identity Providers
Enable login methods through the identities option:
1realm = $realm({2 identities: {3 credentials: true, // email/password (default)4 google: true, // Google OAuth5 github: true, // GitHub OAuth6 },7});
#Securing Actions
Actions are public by default. To require authentication, add the $secure() middleware:
1import { $secure } from "alepha/security"; 2 3publicEndpoint = $action({ 4 handler: () => "anyone can access this", 5}); 6 7protectedEndpoint = $action({ 8 use: [$secure()], 9 handler: () => "only authenticated users",10});
The authenticated user is available on the request object:
1profile = $action({2 path: "/me",3 use: [$secure()],4 handler: async ({ user }) => {5 return user;6 },7});
You can also restrict access to a specific issuer or role:
1adminOnly = $action({2 use: [$secure({ issuers: ["admin"] })],3 handler: () => "admin issuer only",4});5 6managersOnly = $action({7 use: [$secure({ roles: ["manager", "admin"] })],8 handler: () => "managers and admins only",9});
#User Resolution
$secure() resolves the authenticated user using atom-first resolution, which works across all transports:
currentUserAtom— checked first. Set by$action.run()fork, MCP transports, pipelines, and jobs.request.user— HTTP request user set by previous middleware.- HTTP headers — JWT or API key resolved from
Authorizationheader.
#Local Action Calls
When calling an action locally via .run(), pass the user in options:
1// Pass a specific user2await controller.action.run({}, { user: { id: "user-1", roles: ["admin"] } });3 4// Use the system user5await controller.action.run({}, { user: "system" });6 7// Use the user from the current HTTP request8await controller.action.run({}, { user: "context" });
The user is scoped to the action call using ALS fork isolation — it does not leak to subsequent calls.
In test mode, .fetch() automatically creates a JWT token from the user option:
1// Automatic test token creation2const res = await controller.action.fetch({}, { user: { id: "test-user" } });
#Roles and Permissions
$secure() supports authentication-only, role checks, permission checks, and custom guards:
1// Auth only — any authenticated user 2profile = $action({ 3 use: [$secure()], 4 handler: ({ user }) => user, 5}); 6 7// Auth + role check 8dashboard = $action({ 9 use: [$secure({ roles: ["admin"] })],10 handler: () => { /* ... */ },11});12 13// Auth + explicit permissions14deleteOrder = $action({15 use: [$secure({ permissions: ["orders:delete"] })],16 handler: ({ params }) => { /* ... */ },17});18 19// Auth + permissions + issuer restriction20adminManage = $action({21 use: [$secure({ permissions: ["admin:manage"], issuers: ["admin"] })],22 handler: () => { /* ... */ },23});24 25// Auth + custom guard26ownProfile = $action({27 use: [$secure({ guard: (user) => user.id === params.id })],28 handler: () => { /* ... */ },29});
Define roles with permission sets in the issuer:
1issuer = $issuer({ 2 secret: "...", 3 roles: [ 4 { 5 name: "admin", 6 permissions: [{ name: "*" }], 7 }, 8 { 9 name: "editor",10 permissions: [11 { name: "articles:*" },12 { name: "media:upload" },13 { name: "admin:articles:*" },14 ],15 },16 {17 name: "viewer",18 permissions: [19 { name: "articles:list" },20 { name: "articles:get" },21 ],22 },23 ],24});
#Ownership
The ownership flag restricts a permission to resources owned by the user:
1{ 2 name: "user", 3 permissions: [ 4 { 5 name: "*", 6 ownership: true, 7 exclude: ["admin:*"], 8 }, 9 ],10}
This grants access to all actions, but only for the user's own resources. Admin-namespaced actions are excluded entirely.