Stacks Authentication & Authorization
The @stacksjs/auth package provides comprehensive authentication and authorization for Stacks applications, built on @stacksjs/ts-auth.
Key Paths
- Core package source:
storage/framework/core/auth/src/
- Configuration:
config/auth.ts
- Security config:
config/security.ts
- Hashing config:
config/hashing.ts
- Application gates:
app/Gates.ts
- Application middleware:
app/Middleware/
- Middleware aliases:
app/Middleware.ts
- Auth types:
storage/framework/core/types/src/auth.ts
Source Files
auth/src/
├── index.ts # All re-exports
├── authentication.ts # Auth class - core auth logic
├── authenticator.ts # 2FA and personal access client
├── client.ts # Client re-exports
├── middleware.ts # Auth middleware handler
├── rate-limiter.ts # RateLimiter class (5 attempts, 15min lockout)
├── passkey.ts # WebAuthn/Passkey support
├── password/reset.ts # Password reset flow
├── register.ts # User registration
├── user.ts # Auth user helpers
├── tokens.ts # Token CRUD, scopes, refresh tokens, OAuth clients
├── gate.ts # Authorization gates & policies
├── policy.ts # BasePolicy class + discovery
├── authorizable.ts # User authorization mixin
├── rbac.ts # Full RBAC system
├── email-verification.ts # Email verification flow
└── session-auth.ts # Session-based SPA auth
Auth Class (authentication.ts) — Static Methods
Login & Authentication
Auth.attempt(credentials: AuthCredentials): Promise<boolean> — validate credentials without creating token
Auth.validate(credentials: AuthCredentials): Promise<boolean> — alias for attempt
Auth.login(credentials: AuthCredentials, options?: TokenCreateOptions): Promise<{ user, token } | null> — login and create token
Auth.loginUsingId(userId: number, options?: TokenCreateOptions): Promise<{ user, token } | null> — login by user ID
Auth.logout(): Promise<void> — revoke current token
Auth.once(credentials: AuthCredentials): Promise<boolean> — one-time auth without token
Auth.requestToken(credentials, clientId, clientSecret): Promise<{ token } | null> — OAuth token request
User State
Auth.user(): Promise<UserModel | undefined> — get authenticated user from bearer token
Auth.check(): Promise<boolean> — is user authenticated?
Auth.guest(): Promise<boolean> — is user a guest?
Auth.id(): Promise<number | undefined> — get authenticated user ID
Auth.setUser(user: UserModel): void — manually set user
Token Creation
Auth.createTokenForUser(user, options?: TokenCreateOptions): Promise<NewAccessToken>
Auth.createToken(user, name?, abilities?): Promise<AuthToken>
Token Validation
Auth.validateToken(token: string): Promise<boolean> — validate bearer token
Auth.getUserFromToken(token: string): Promise<UserModel | undefined>
Auth.currentAccessToken(): Promise<PersonalAccessToken | undefined>
Token Abilities (Scopes)
Auth.tokenCan(ability: string): Promise<boolean>
Auth.tokenCant(ability: string): Promise<boolean>
Auth.tokenAbilities(): Promise<string[]>
Auth.tokenCanAll(abilities: string[]): Promise<boolean>
Auth.tokenCanAny(abilities: string[]): Promise<boolean>
Token Management
Auth.tokens(userId?: number): Promise<PersonalAccessToken[]>
Auth.revokeToken(token: string): Promise<void>
Auth.revokeTokenById(tokenId: number): Promise<void>
Auth.revokeAllTokens(userId?: number): Promise<void>
Auth.revokeOtherTokens(userId?: number): Promise<void>
Auth.pruneExpiredTokens(): Promise<number>
Auth.pruneRevokedTokens(): Promise<number>
Auth.rotateToken(oldToken: string): Promise<AuthToken | null>
Auth.findToken(tokenId: number): Promise<PersonalAccessToken | null>
Utility
Auth.guard(name?: string): typeof Auth — select guard (returns self)
Auth.viaRemember(): boolean — always false currently
Auth.clearState(): void — clear cached user/token
Token System (tokens.ts)
Access Tokens
tokens(userId: number): Promise<AccessToken[]>
findToken(plainTextToken: string): Promise<AccessToken | null>
currentAccessToken(): Promise<AccessToken | null>
createToken(userId, name?, scopes?, options?): Promise<PersonalAccessTokenResult>
- Options:
{ expiresInMinutes?, withRefreshToken?, refreshExpiresInDays? }
Refresh Tokens
refreshToken(refreshTokenPlain, options?): Promise<RefreshTokenResult>
validateRefreshToken(refreshTokenPlain): Promise<boolean>
revokeRefreshToken(refreshTokenPlain): Promise<void>
revokeAllRefreshTokens(userId): Promise<void>
deleteExpiredRefreshTokens(): Promise<number>
deleteRevokedRefreshTokens(daysOld?): Promise<number>
Token Revocation
revokeToken(plainTextToken): Promise<void>
revokeTokenById(tokenId): Promise<void>
revokeAllTokens(userId): Promise<void>
revokeOtherTokens(userId): Promise<void>
deleteExpiredTokens(): Promise<number>
deleteRevokedTokens(daysOld?): Promise<number>
Token Scopes
tokenCan(scope): Promise<boolean>
tokenCant(scope): Promise<boolean>
tokenCanAll(scopes): Promise<boolean>
tokenCanAny(scopes): Promise<boolean>
tokenAbilities(): Promise<string[]>
parseScopes(scopes: string | string[] | null | undefined): TokenScopes
OAuth Clients
clients(userId): Promise<OAuthClient[]>
findClient(clientId): Promise<OAuthClient | null>
createClient(options: CreateClientOptions): Promise<CreateClientResult>
revokeClient(clientId): Promise<void>
Two-Factor Authentication (authenticator.ts)
generateTwoFactorSecret(): string
generateTwoFactorToken(secret: Secret): Promise<Token>
verifyTwoFactorCode(token: Token, secret: Secret): Promise<boolean>
generateTwoFactorUri(user?, service?, secret?): string
createPersonalAccessClient(): Promise<Result<string, never>>
Re-exported from @stacksjs/ts-auth:
generateTOTP, verifyTOTP, generateTOTPSecret, totpKeyUri
Authorization Gates (gate.ts)
Gate Functions
define<T>(ability: string, callback: GateCallback<T>): void
policy(model: string | { name }, policyClass: new () => Policy): void
before(callback): void — run before any gate check
after(callback): void — run after any gate check
allows(ability, user, ...args): Promise<boolean>
denies(ability, user, ...args): Promise<boolean>
can(ability, user, ...args): Promise<boolean>
cannot(ability, user, ...args): Promise<boolean>
any(abilities[], user, ...args): Promise<boolean>
all(abilities[], user, ...args): Promise<boolean>
none(abilities[], user, ...args): Promise<boolean>
authorize(ability, user, ...args): Promise<AuthorizationResponse> — throws on deny
inspect(ability, user, ...args): Promise<AuthorizationResponse> — never throws
has(ability): boolean
hasPolicy(model): boolean
abilities(): string[]
getPolicyFor<T>(model: T): Policy<T> | null
flush(): void — clear all gates
Gate Facade — Gate.define(), Gate.can(), etc.
AuthorizationResponse Class
static allow(message?): AuthorizationResponse
static deny(message?, code?): AuthorizationResponse
allowed(): boolean, denied(): boolean
authorize(): void — throws AuthorizationException if denied
Policy Interface
Methods: before?, viewAny?, view?, create?, update?, delete?, restore?, forceDelete?
BasePolicy Abstract Class
Protected helpers: allow(message?), deny(message?, code?), denyIf(condition), denyUnless(condition), allowIf(condition)
RBAC System (rbac.ts)
Role Management
Rbac.createRole(name, guardName?, description?): Promise<RoleRecord>
Rbac.findRole(name, guardName?): Promise<RoleRecord | null>
Rbac.deleteRole(name, guardName?): Promise<void>
Rbac.getAllRoles(guardName?): Promise<RoleRecord[]>
Permission Management
Rbac.createPermission(name, guardName?, description?): Promise<PermissionRecord>
Rbac.findPermission(name, guardName?): Promise<PermissionRecord | null>
Rbac.deletePermission(name, guardName?): Promise<void>
Rbac.getAllPermissions(guardName?): Promise<PermissionRecord[]>
User-Role Operations
Rbac.getUserRoles(user): Promise<RoleRecord[]>
Rbac.assignRole(user, roleName, guardName?): Promise<void>
Rbac.removeRole(user, roleName, guardName?): Promise<void>
Rbac.removeAllRoles(user): Promise<void>
Rbac.syncRoles(user, roleNames[], guardName?): Promise<void>
Rbac.hasRole(user, roleName, guardName?): Promise<boolean>
Rbac.hasAnyRole(user, roleNames[], guardName?): Promise<boolean>
Rbac.hasAllRoles(user, roleNames[], guardName?): Promise<boolean>
User-Permission Operations
Rbac.getUserPermissions(user): Promise<PermissionRecord[]>
Rbac.givePermission(user, permissionName, guardName?): Promise<void>
Rbac.revokePermission(user, permissionName, guardName?): Promise<void>
Rbac.revokeAllPermissions(user): Promise<void>
Rbac.syncPermissions(user, permissionNames[], guardName?): Promise<void>
Rbac.hasPermission(user, permissionName, guardName?): Promise<boolean>
Rbac.hasAnyPermission(user, permissionNames[], guardName?): Promise<boolean>
Rbac.hasAllPermissions(user, permissionNames[], guardName?): Promise<boolean>
Role-Permission Operations
Rbac.getRolePermissions(roleId): Promise<PermissionRecord[]>
Rbac.givePermissionToRole(roleName, permissionName, guardName?): Promise<void>
Rbac.revokePermissionFromRole(roleName, permissionName, guardName?): Promise<void>
Rbac.syncRolePermissions(roleName, permissionNames[], guardName?): Promise<void>
withRbac Mixin
withRbac(user) — adds hasRole(), hasPermission(), assignRole(), givePermission(), etc. to any user object
RBAC Types
interface RoleRecord { id, name, guard_name, description?, created_at?, updated_at? }
interface PermissionRecord { id, name, guard_name, description?, created_at?, updated_at? }
interface RbacStore { findRoleByName, createRole, deleteRole, getAllRoles, findPermissionByName, createPermission, ... }
Session Auth (session-auth.ts)
SessionAuth.login(email, password): Promise<{ user, sessionId }>
SessionAuth.logout(sessionId): void
SessionAuth.user(sessionId): Promise<UserModel | undefined>
SessionAuth.check(sessionId): boolean
SessionAuth.refresh(sessionId, ttlMs?): boolean
Internal: in-memory Map with 10k session limit, 5-minute eviction interval, timing-safe password comparison with dummy bcrypt hash.
Email Verification (email-verification.ts)
EmailVerification.isVerified(user): boolean
EmailVerification.send(user): Promise<void>
EmailVerification.verify(userId, token): Promise<EmailVerificationResult>
EmailVerification.resend(user): Promise<EmailVerificationResult>
Password Reset (password/reset.ts)
const actions = passwordResets(email)
await actions.sendEmail()
const valid = await actions.verifyToken(token)
const result = await actions.resetPassword(token, newPassword)
Registration (register.ts)
register(credentials: NewUser): Promise<{ token: AuthToken }>
User Helpers (user.ts)
authUser(): Promise<UserModel | undefined>
check(): Promise<boolean>
id(): Promise<number | undefined>
email(): Promise<string | undefined>
name(): Promise<string | undefined>
isAuthenticated(): Promise<boolean>
logout(): Promise<void>
refresh(): Promise<void>
Passkey/WebAuthn (passkey.ts)
getUserPasskeys(userId): Promise<PasskeyAttribute[]>
getUserPasskey(userId, passkeyId): Promise<PasskeyAttribute | undefined>
setCurrentRegistrationOptions(user, verified): Promise<void>
Re-exported from @stacksjs/ts-auth:
generateRegistrationOptions, generateAuthenticationOptions
verifyRegistrationResponse, verifyAuthenticationResponse
startRegistration, startAuthentication (browser)
browserSupportsWebAuthn, browserSupportsWebAuthnAutofill
platformAuthenticatorIsAvailable
Auth Middleware (middleware.ts)
export const authMiddlewareHandler = {
name: 'auth',
handle: authMiddleware, // validates bearer token, throws 401
}
Rate Limiter (rate-limiter.ts)
class RateLimiter {
static MAX_ATTEMPTS = 5
static LOCKOUT_DURATION = 15 * 60 * 1000 // 15 minutes
static MAX_STORE_SIZE = 10_000
static EVICTION_INTERVAL = 5 * 60 * 1000 // 5 minutes
static isRateLimited(email): boolean
static recordFailedAttempt(email): void
static resetAttempts(email): void
static validateAttempt(email): void // throws HttpError 429
}
Authorizable Mixin (authorizable.ts)
const authUser = withAuthorization(user)
await authUser.can('edit-post', post)
await authUser.cannot('delete-post', post)
await authUser.canAny(['edit', 'delete'], post)
await authUser.canAll(['edit', 'publish'], post)
await authUser.authorize('edit-post', post) // throws if denied
Configuration
config/auth.ts
{
default: 'api',
guards: { api: { driver: 'token', provider: 'users' } },
providers: { users: { driver: 'database', table: 'users' } },
username: 'email', // AUTH_USERNAME_FIELD env
password: 'password', // AUTH_PASSWORD_FIELD env
tokenExpiry: 30, // days, AUTH_TOKEN_EXPIRY env
tokenRotation: 7, // days, AUTH_TOKEN_ROTATION env
defaultAbilities: ['*'],
defaultTokenName: 'auth_token',
passwordReset: { expire: 60, throttle: 60 }
}
config/hashing.ts
{
driver: 'bcrypt', // 'bcrypt' | 'argon2'
bcrypt: { rounds: 12 },
argon2: { memory: 65536, time: 3 }
}
config/security.ts
{
firewall: {
enabled: true,
countryCodes: [],
ipAddresses: { allowlist: [], blocklist: [] },
rateLimitPerMinute: 500,
useIpReputationLists: true,
useKnownBadInputsRuleSet: true
}
}
Middleware Aliases (app/Middleware.ts)
Available middleware names: maintenance, auth, guest, api, team, logger, abilities, can, throttle, local, development, staging, production, env.local, env.development, env.staging, env.production, role, permission, verified (EnsureEmailIsVerified)
Application Gates Example (app/Gates.ts)
Gate.define('access-admin', (user) => user?.email?.endsWith('@stacksjs.org') ?? false)
Gate.define('edit-settings', (user) => !!user)
Gate.define('view-dashboard', (user) => !!user)
Default API Routes
POST /login → LoginAction (validates email + password)
POST /register → RegisterAction
POST /auth/refresh → RefreshTokenAction
POST /auth/token → CreateTokenAction
GET /auth/tokens → ListTokensAction (auth middleware)
DELETE /auth/tokens/{id} → RevokeTokenAction (auth middleware)
GET /me → GetMeAction (auth middleware)
POST /logout → LogoutAction (auth middleware)
User Model Traits
// User model uses:
traits: {
useAuth: { usePasskey: true },
useUuid: true,
useTimestamps: true,
useSocials: ['github'],
}
Gotchas
- Auth depends on
@stacksjs/ts-auth for TOTP and passkey functions
- Password hashing defaults to bcrypt with 12 rounds (config/hashing.ts)
- Rate limiter uses in-memory Map, resets on server restart — not shared across workers
- Session auth also uses in-memory Map with 10k limit — for SPA cookie auth
- Token format is
tokenId|plainText — the | separates the encrypted ID from the plain token
- The
parseToken() helper splits on | to extract both parts
- Bearer tokens come from the
Authorization: Bearer <token> header
Auth.user() internally calls getBearerToken() → parseToken() → getTokenFromId() → validates hash
- RBAC has an internal cache (
userRoles, userPermissions, rolePermissions) — call Rbac.flushCache() after direct DB changes
- Gate
before callbacks can short-circuit — return true to allow, null to continue checking
withRbac() and withAuthorization() return new objects with methods mixed in
- The
RbacStore interface must be implemented and set via Rbac.setStore() for RBAC to work
- Password reset tokens expire after 60 minutes by default
- Default token abilities are
['*'] — wildcard access
- Token expiry defaults to 30 days
- Session auth uses timing-safe bcrypt comparison even for failed lookups (dummy hash prevents timing attacks)
Build
cd storage/framework/core/auth && bun build.ts
1---2name: stacks-auth3description: Use when implementing authentication, authorization, passkeys, TOTP/2FA, RBAC, gates, policies, session auth, token management, email verification, password resets, or rate limiting in a Stacks application. Covers the @stacksjs/auth package, config/auth.ts, app/Gates.ts, and app/Middleware/.4license: MIT5---67# Stacks Authentication & Authorization89The `@stacksjs/auth` package provides comprehensive authentication and authorization for Stacks applications, built on `@stacksjs/ts-auth`.1011## Key Paths1213- Core package source: `storage/framework/core/auth/src/`14- Configuration: `config/auth.ts`15- Security config: `config/security.ts`16- Hashing config: `config/hashing.ts`17- Application gates: `app/Gates.ts`18- Application middleware: `app/Middleware/`19- Middleware aliases: `app/Middleware.ts`20- Auth types: `storage/framework/core/types/src/auth.ts`2122## Source Files2324```25auth/src/26├── index.ts # All re-exports27├── authentication.ts # Auth class - core auth logic28├── authenticator.ts # 2FA and personal access client29├── client.ts # Client re-exports30├── middleware.ts # Auth middleware handler31├── rate-limiter.ts # RateLimiter class (5 attempts, 15min lockout)32├── passkey.ts # WebAuthn/Passkey support33├── password/reset.ts # Password reset flow34├── register.ts # User registration35├── user.ts # Auth user helpers36├── tokens.ts # Token CRUD, scopes, refresh tokens, OAuth clients37├── gate.ts # Authorization gates & policies38├── policy.ts # BasePolicy class + discovery39├── authorizable.ts # User authorization mixin40├── rbac.ts # Full RBAC system41├── email-verification.ts # Email verification flow42└── session-auth.ts # Session-based SPA auth43```4445## Auth Class (authentication.ts) — Static Methods4647### Login & Authentication48- `Auth.attempt(credentials: AuthCredentials): Promise<boolean>` — validate credentials without creating token49- `Auth.validate(credentials: AuthCredentials): Promise<boolean>` — alias for attempt50- `Auth.login(credentials: AuthCredentials, options?: TokenCreateOptions): Promise<{ user, token } | null>` — login and create token51- `Auth.loginUsingId(userId: number, options?: TokenCreateOptions): Promise<{ user, token } | null>` — login by user ID52- `Auth.logout(): Promise<void>` — revoke current token53- `Auth.once(credentials: AuthCredentials): Promise<boolean>` — one-time auth without token54- `Auth.requestToken(credentials, clientId, clientSecret): Promise<{ token } | null>` — OAuth token request5556### User State57- `Auth.user(): Promise<UserModel | undefined>` — get authenticated user from bearer token58- `Auth.check(): Promise<boolean>` — is user authenticated?59- `Auth.guest(): Promise<boolean>` — is user a guest?60- `Auth.id(): Promise<number | undefined>` — get authenticated user ID61- `Auth.setUser(user: UserModel): void` — manually set user6263### Token Creation64- `Auth.createTokenForUser(user, options?: TokenCreateOptions): Promise<NewAccessToken>`65- `Auth.createToken(user, name?, abilities?): Promise<AuthToken>`6667### Token Validation68- `Auth.validateToken(token: string): Promise<boolean>` — validate bearer token69- `Auth.getUserFromToken(token: string): Promise<UserModel | undefined>`70- `Auth.currentAccessToken(): Promise<PersonalAccessToken | undefined>`7172### Token Abilities (Scopes)73- `Auth.tokenCan(ability: string): Promise<boolean>`74- `Auth.tokenCant(ability: string): Promise<boolean>`75- `Auth.tokenAbilities(): Promise<string[]>`76- `Auth.tokenCanAll(abilities: string[]): Promise<boolean>`77- `Auth.tokenCanAny(abilities: string[]): Promise<boolean>`7879### Token Management80- `Auth.tokens(userId?: number): Promise<PersonalAccessToken[]>`81- `Auth.revokeToken(token: string): Promise<void>`82- `Auth.revokeTokenById(tokenId: number): Promise<void>`83- `Auth.revokeAllTokens(userId?: number): Promise<void>`84- `Auth.revokeOtherTokens(userId?: number): Promise<void>`85- `Auth.pruneExpiredTokens(): Promise<number>`86- `Auth.pruneRevokedTokens(): Promise<number>`87- `Auth.rotateToken(oldToken: string): Promise<AuthToken | null>`88- `Auth.findToken(tokenId: number): Promise<PersonalAccessToken | null>`8990### Utility91- `Auth.guard(name?: string): typeof Auth` — select guard (returns self)92- `Auth.viaRemember(): boolean` — always false currently93- `Auth.clearState(): void` — clear cached user/token9495## Token System (tokens.ts)9697### Access Tokens98- `tokens(userId: number): Promise<AccessToken[]>`99- `findToken(plainTextToken: string): Promise<AccessToken | null>`100- `currentAccessToken(): Promise<AccessToken | null>`101- `createToken(userId, name?, scopes?, options?): Promise<PersonalAccessTokenResult>`102 - Options: `{ expiresInMinutes?, withRefreshToken?, refreshExpiresInDays? }`103104### Refresh Tokens105- `refreshToken(refreshTokenPlain, options?): Promise<RefreshTokenResult>`106- `validateRefreshToken(refreshTokenPlain): Promise<boolean>`107- `revokeRefreshToken(refreshTokenPlain): Promise<void>`108- `revokeAllRefreshTokens(userId): Promise<void>`109- `deleteExpiredRefreshTokens(): Promise<number>`110- `deleteRevokedRefreshTokens(daysOld?): Promise<number>`111112### Token Revocation113- `revokeToken(plainTextToken): Promise<void>`114- `revokeTokenById(tokenId): Promise<void>`115- `revokeAllTokens(userId): Promise<void>`116- `revokeOtherTokens(userId): Promise<void>`117- `deleteExpiredTokens(): Promise<number>`118- `deleteRevokedTokens(daysOld?): Promise<number>`119120### Token Scopes121- `tokenCan(scope): Promise<boolean>`122- `tokenCant(scope): Promise<boolean>`123- `tokenCanAll(scopes): Promise<boolean>`124- `tokenCanAny(scopes): Promise<boolean>`125- `tokenAbilities(): Promise<string[]>`126- `parseScopes(scopes: string | string[] | null | undefined): TokenScopes`127128### OAuth Clients129- `clients(userId): Promise<OAuthClient[]>`130- `findClient(clientId): Promise<OAuthClient | null>`131- `createClient(options: CreateClientOptions): Promise<CreateClientResult>`132- `revokeClient(clientId): Promise<void>`133134## Two-Factor Authentication (authenticator.ts)135136- `generateTwoFactorSecret(): string`137- `generateTwoFactorToken(secret: Secret): Promise<Token>`138- `verifyTwoFactorCode(token: Token, secret: Secret): Promise<boolean>`139- `generateTwoFactorUri(user?, service?, secret?): string`140- `createPersonalAccessClient(): Promise<Result<string, never>>`141142### Re-exported from @stacksjs/ts-auth:143- `generateTOTP`, `verifyTOTP`, `generateTOTPSecret`, `totpKeyUri`144145## Authorization Gates (gate.ts)146147### Gate Functions148- `define<T>(ability: string, callback: GateCallback<T>): void`149- `policy(model: string | { name }, policyClass: new () => Policy): void`150- `before(callback): void` — run before any gate check151- `after(callback): void` — run after any gate check152- `allows(ability, user, ...args): Promise<boolean>`153- `denies(ability, user, ...args): Promise<boolean>`154- `can(ability, user, ...args): Promise<boolean>`155- `cannot(ability, user, ...args): Promise<boolean>`156- `any(abilities[], user, ...args): Promise<boolean>`157- `all(abilities[], user, ...args): Promise<boolean>`158- `none(abilities[], user, ...args): Promise<boolean>`159- `authorize(ability, user, ...args): Promise<AuthorizationResponse>` — throws on deny160- `inspect(ability, user, ...args): Promise<AuthorizationResponse>` — never throws161- `has(ability): boolean`162- `hasPolicy(model): boolean`163- `abilities(): string[]`164- `getPolicyFor<T>(model: T): Policy<T> | null`165- `flush(): void` — clear all gates166167### Gate Facade — `Gate.define()`, `Gate.can()`, etc.168169### AuthorizationResponse Class170- `static allow(message?): AuthorizationResponse`171- `static deny(message?, code?): AuthorizationResponse`172- `allowed(): boolean`, `denied(): boolean`173- `authorize(): void` — throws AuthorizationException if denied174175### Policy Interface176Methods: `before?`, `viewAny?`, `view?`, `create?`, `update?`, `delete?`, `restore?`, `forceDelete?`177178### BasePolicy Abstract Class179Protected helpers: `allow(message?)`, `deny(message?, code?)`, `denyIf(condition)`, `denyUnless(condition)`, `allowIf(condition)`180181## RBAC System (rbac.ts)182183### Role Management184- `Rbac.createRole(name, guardName?, description?): Promise<RoleRecord>`185- `Rbac.findRole(name, guardName?): Promise<RoleRecord | null>`186- `Rbac.deleteRole(name, guardName?): Promise<void>`187- `Rbac.getAllRoles(guardName?): Promise<RoleRecord[]>`188189### Permission Management190- `Rbac.createPermission(name, guardName?, description?): Promise<PermissionRecord>`191- `Rbac.findPermission(name, guardName?): Promise<PermissionRecord | null>`192- `Rbac.deletePermission(name, guardName?): Promise<void>`193- `Rbac.getAllPermissions(guardName?): Promise<PermissionRecord[]>`194195### User-Role Operations196- `Rbac.getUserRoles(user): Promise<RoleRecord[]>`197- `Rbac.assignRole(user, roleName, guardName?): Promise<void>`198- `Rbac.removeRole(user, roleName, guardName?): Promise<void>`199- `Rbac.removeAllRoles(user): Promise<void>`200- `Rbac.syncRoles(user, roleNames[], guardName?): Promise<void>`201- `Rbac.hasRole(user, roleName, guardName?): Promise<boolean>`202- `Rbac.hasAnyRole(user, roleNames[], guardName?): Promise<boolean>`203- `Rbac.hasAllRoles(user, roleNames[], guardName?): Promise<boolean>`204205### User-Permission Operations206- `Rbac.getUserPermissions(user): Promise<PermissionRecord[]>`207- `Rbac.givePermission(user, permissionName, guardName?): Promise<void>`208- `Rbac.revokePermission(user, permissionName, guardName?): Promise<void>`209- `Rbac.revokeAllPermissions(user): Promise<void>`210- `Rbac.syncPermissions(user, permissionNames[], guardName?): Promise<void>`211- `Rbac.hasPermission(user, permissionName, guardName?): Promise<boolean>`212- `Rbac.hasAnyPermission(user, permissionNames[], guardName?): Promise<boolean>`213- `Rbac.hasAllPermissions(user, permissionNames[], guardName?): Promise<boolean>`214215### Role-Permission Operations216- `Rbac.getRolePermissions(roleId): Promise<PermissionRecord[]>`217- `Rbac.givePermissionToRole(roleName, permissionName, guardName?): Promise<void>`218- `Rbac.revokePermissionFromRole(roleName, permissionName, guardName?): Promise<void>`219- `Rbac.syncRolePermissions(roleName, permissionNames[], guardName?): Promise<void>`220221### withRbac Mixin222`withRbac(user)` — adds `hasRole()`, `hasPermission()`, `assignRole()`, `givePermission()`, etc. to any user object223224### RBAC Types225```typescript226interface RoleRecord { id, name, guard_name, description?, created_at?, updated_at? }227interface PermissionRecord { id, name, guard_name, description?, created_at?, updated_at? }228interface RbacStore { findRoleByName, createRole, deleteRole, getAllRoles, findPermissionByName, createPermission, ... }229```230231## Session Auth (session-auth.ts)232233- `SessionAuth.login(email, password): Promise<{ user, sessionId }>`234- `SessionAuth.logout(sessionId): void`235- `SessionAuth.user(sessionId): Promise<UserModel | undefined>`236- `SessionAuth.check(sessionId): boolean`237- `SessionAuth.refresh(sessionId, ttlMs?): boolean`238239Internal: in-memory Map with 10k session limit, 5-minute eviction interval, timing-safe password comparison with dummy bcrypt hash.240241## Email Verification (email-verification.ts)242243- `EmailVerification.isVerified(user): boolean`244- `EmailVerification.send(user): Promise<void>`245- `EmailVerification.verify(userId, token): Promise<EmailVerificationResult>`246- `EmailVerification.resend(user): Promise<EmailVerificationResult>`247248## Password Reset (password/reset.ts)249250```typescript251const actions = passwordResets(email)252await actions.sendEmail()253const valid = await actions.verifyToken(token)254const result = await actions.resetPassword(token, newPassword)255```256257## Registration (register.ts)258259- `register(credentials: NewUser): Promise<{ token: AuthToken }>`260261## User Helpers (user.ts)262263- `authUser(): Promise<UserModel | undefined>`264- `check(): Promise<boolean>`265- `id(): Promise<number | undefined>`266- `email(): Promise<string | undefined>`267- `name(): Promise<string | undefined>`268- `isAuthenticated(): Promise<boolean>`269- `logout(): Promise<void>`270- `refresh(): Promise<void>`271272## Passkey/WebAuthn (passkey.ts)273274- `getUserPasskeys(userId): Promise<PasskeyAttribute[]>`275- `getUserPasskey(userId, passkeyId): Promise<PasskeyAttribute | undefined>`276- `setCurrentRegistrationOptions(user, verified): Promise<void>`277278### Re-exported from @stacksjs/ts-auth:279- `generateRegistrationOptions`, `generateAuthenticationOptions`280- `verifyRegistrationResponse`, `verifyAuthenticationResponse`281- `startRegistration`, `startAuthentication` (browser)282- `browserSupportsWebAuthn`, `browserSupportsWebAuthnAutofill`283- `platformAuthenticatorIsAvailable`284285## Auth Middleware (middleware.ts)286287```typescript288export const authMiddlewareHandler = {289 name: 'auth',290 handle: authMiddleware, // validates bearer token, throws 401291}292```293294## Rate Limiter (rate-limiter.ts)295296```typescript297class RateLimiter {298 static MAX_ATTEMPTS = 5299 static LOCKOUT_DURATION = 15 * 60 * 1000 // 15 minutes300 static MAX_STORE_SIZE = 10_000301 static EVICTION_INTERVAL = 5 * 60 * 1000 // 5 minutes302303 static isRateLimited(email): boolean304 static recordFailedAttempt(email): void305 static resetAttempts(email): void306 static validateAttempt(email): void // throws HttpError 429307}308```309310## Authorizable Mixin (authorizable.ts)311312```typescript313const authUser = withAuthorization(user)314await authUser.can('edit-post', post)315await authUser.cannot('delete-post', post)316await authUser.canAny(['edit', 'delete'], post)317await authUser.canAll(['edit', 'publish'], post)318await authUser.authorize('edit-post', post) // throws if denied319```320321## Configuration322323### config/auth.ts324```typescript325{326 default: 'api',327 guards: { api: { driver: 'token', provider: 'users' } },328 providers: { users: { driver: 'database', table: 'users' } },329 username: 'email', // AUTH_USERNAME_FIELD env330 password: 'password', // AUTH_PASSWORD_FIELD env331 tokenExpiry: 30, // days, AUTH_TOKEN_EXPIRY env332 tokenRotation: 7, // days, AUTH_TOKEN_ROTATION env333 defaultAbilities: ['*'],334 defaultTokenName: 'auth_token',335 passwordReset: { expire: 60, throttle: 60 }336}337```338339### config/hashing.ts340```typescript341{342 driver: 'bcrypt', // 'bcrypt' | 'argon2'343 bcrypt: { rounds: 12 },344 argon2: { memory: 65536, time: 3 }345}346```347348### config/security.ts349```typescript350{351 firewall: {352 enabled: true,353 countryCodes: [],354 ipAddresses: { allowlist: [], blocklist: [] },355 rateLimitPerMinute: 500,356 useIpReputationLists: true,357 useKnownBadInputsRuleSet: true358 }359}360```361362## Middleware Aliases (app/Middleware.ts)363364Available middleware names: `maintenance`, `auth`, `guest`, `api`, `team`, `logger`, `abilities`, `can`, `throttle`, `local`, `development`, `staging`, `production`, `env.local`, `env.development`, `env.staging`, `env.production`, `role`, `permission`, `verified` (EnsureEmailIsVerified)365366## Application Gates Example (app/Gates.ts)367368```typescript369Gate.define('access-admin', (user) => user?.email?.endsWith('@stacksjs.org') ?? false)370Gate.define('edit-settings', (user) => !!user)371Gate.define('view-dashboard', (user) => !!user)372```373374## Default API Routes375376- `POST /login` → LoginAction (validates email + password)377- `POST /register` → RegisterAction378- `POST /auth/refresh` → RefreshTokenAction379- `POST /auth/token` → CreateTokenAction380- `GET /auth/tokens` → ListTokensAction (auth middleware)381- `DELETE /auth/tokens/{id}` → RevokeTokenAction (auth middleware)382- `GET /me` → GetMeAction (auth middleware)383- `POST /logout` → LogoutAction (auth middleware)384385## User Model Traits386387```typescript388// User model uses:389traits: {390 useAuth: { usePasskey: true },391 useUuid: true,392 useTimestamps: true,393 useSocials: ['github'],394}395```396397## Gotchas398399- Auth depends on `@stacksjs/ts-auth` for TOTP and passkey functions400- Password hashing defaults to bcrypt with 12 rounds (config/hashing.ts)401- Rate limiter uses in-memory Map, resets on server restart — not shared across workers402- Session auth also uses in-memory Map with 10k limit — for SPA cookie auth403- Token format is `tokenId|plainText` — the `|` separates the encrypted ID from the plain token404- The `parseToken()` helper splits on `|` to extract both parts405- Bearer tokens come from the `Authorization: Bearer <token>` header406- `Auth.user()` internally calls `getBearerToken()` → `parseToken()` → `getTokenFromId()` → validates hash407- RBAC has an internal cache (`userRoles`, `userPermissions`, `rolePermissions`) — call `Rbac.flushCache()` after direct DB changes408- Gate `before` callbacks can short-circuit — return `true` to allow, `null` to continue checking409- `withRbac()` and `withAuthorization()` return new objects with methods mixed in410- The `RbacStore` interface must be implemented and set via `Rbac.setStore()` for RBAC to work411- Password reset tokens expire after 60 minutes by default412- Default token abilities are `['*']` — wildcard access413- Token expiry defaults to 30 days414- Session auth uses timing-safe bcrypt comparison even for failed lookups (dummy hash prevents timing attacks)415416## Build417418```bash419cd storage/framework/core/auth && bun build.ts420```