SERVER / SESSIONS
Opaque sessions
Bearer describes how a credential travels. It does not require JWT. Dowe's CRUD template uses a server-owned ULID session backed by Cache and Database.
1 / MENTAL MODEL
Use an opaque ULID as the session id
id session source:"ulid" generates a canonical 26-character ULID on the server. The client receives only the opaque id and sends it as Authorization: Bearer <ulid>. Claims and database credentials never enter the token.
fn createSessionRepository params:{ userId:string }
id session source:"ulid"
query created conn:appDb.insert table:"sessions" value:{ id:session userId:args.userId createdAt:now } required:["id" "userId"]
str sessionKey source:"join" values:["session" session] delimiter:":"
kv cached conn:appCache.set key:sessionKey value:{ id:session userId:args.userId }
return value:{ id:session userId:args.userId }Layer | Responsibility |
|---|---|
No dataThere are no records to display | |
2 / MIDDLEWARE
Validate Cache first, then Database
session.verify rejects malformed or expired ULIDs, checks Cache, falls back to the sessions table, and returns valid:false when the session is absent. Only verified data should reach next context.
import { appDb, appCache } from "@/server/config/database"
middleware requireBearer
bearer token value:req.header.Authorization
session verified cache:appCache database:appDb token:token maxAge:2592000
if verified.valid
next context:{ auth:{ subject:verified.userId session:verified.id authorization:req.header.Authorization token:token } }
return status:401 json:{ ok:false error:"Unauthorized" }Step | Result |
|---|---|
No dataThere are no records to display | |
3 / LIFECYCLE
Expiration and logout are stateful
The default CRUD maxAge is 30 days. Runtime checks the ULID timestamp on every validation. Logout deletes both session:<ulid> from Cache and the matching sessions record, so revocation is immediate.
fn logoutUserService params:{ session:string }
deleteSessionRepository deleted args:{ id:args.session }
return value:{ authenticated:false guest:true authorization:"" token:"" }Concern | Rule |
|---|---|
No dataThere are no records to display | |
4 / DESIGN CHOICE
When should you use JWT?
Choose the credential model from the authority your application needs: shared state favors immediate revocation, while signed assertions favor independent verification.
Model | Choose it when |
|---|---|
No dataThere are no records to display | |