Sample: Session Revocation
I want JWTs to be revocable server-side — logout, password change, or admin force-logout should invalidate outstanding tokens before they expire.
What this sample shows
Section titled “What this sample shows”The production wiring shape: an app builds createSessionCallbacks() against a live DB handle, then hands the creator, revoker, and checker to buildServer({ auth: ... }). From there the framework handles the rest — login persists a sid, the middleware checks it on every request, logout flips the DB row, and a forged JWT without a matching session is rejected.
The wiring in 3 sentences
Section titled “The wiring in 3 sentences”createSessionCallbacks({ db })returnssessionCreator,sessionRevoker, andsessionCheckerbacked byread_user_sessions.buildServer({ auth: { sessionCreator, sessionRevoker, sessionChecker, ... } })links the JWTjticlaim to that table on every request.- After logout (or mass-revoke on password change), the same JWT returns 401 even though the signature is still valid.
What to copy into your app
Section titled “What to copy into your app”const db = createDbConnection(config.databaseUrl);const callbacks = createSessionCallbacks({ db });
const server = buildServer({ registry: buildRegistry([ createSessionsFeature({ autoRevokeOnPasswordChange: callbacks.sessionMassRevoker, }), // ... other features ]), context: { db, /* ... */ }, jwtSecret: process.env.JWT_SECRET, auth: { membershipQuery: "tenant:query:memberships", loginHandler: "auth-email-password:write:login", sessionCreator: callbacks.sessionCreator, sessionRevoker: callbacks.sessionRevoker, sessionChecker: callbacks.sessionChecker, },});Design rules
Section titled “Design rules”- Sessions are a feature, not built-in. Without
createSessionsFeature()and wired callbacks, the app issues plain stateless JWTs — valid until expiry, no revocation path. - Storage is pluggable. This sample uses the default DB-backed impl; swap in Redis or Memcached by implementing the same
SessionCreator/SessionRevoker/SessionCheckersignatures. jtiis the link. When a checker is wired, the middleware never trusts the JWT alone — it confirms the session row is still live.
- End-to-end: login → authenticated query → logout → same token rejected with 401
- Wiring contract: copies the
buildServer({ auth: ... })block from the sample comment verbatim
Source code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
// Session Revocation Sample//// Shows the production-wiring shape: an app builds `createSessionCallbacks()`// against a live DB handle, then hands the creator/revoker/checker to// `buildServer({ auth: ... })`. From there the framework handles the rest —// login persists a sid, the middleware checks it on every request, logout// flips the DB row, a forged JWT without a matching session is rejected.//// What to copy into your own app://// const db = createDbConnection(config.databaseUrl);// const callbacks = createSessionCallbacks({ db });// const server = buildServer({// registry: buildRegistry([// // Pass the mass-revoker into the sessions feature to wire// // "password-change signs you out everywhere" — the feature registers// // a cross-feature entity-hook on the user entity that runs it.// createSessionsFeature({// autoRevokeOnPasswordChange: callbacks.sessionMassRevoker,// }),// // ... other features// ]),// context: { db, ... },// jwtSecret: config.jwtSecret,// auth: {// membershipQuery: "tenant:query:memberships",// loginHandler: "auth-email-password:write:login",// sessionCreator: callbacks.sessionCreator,// sessionRevoker: callbacks.sessionRevoker,// sessionChecker: callbacks.sessionChecker,// },// });//// Design rules the sample demonstrates://// 1. Sessions are a feature, not built-in. An app that doesn't register// `createSessionsFeature()` (or doesn't wire callbacks) issues plain// stateless JWTs — valid until expiry, no revocation path.// 2. Session storage is abstracted behind the three callback signatures.// This sample uses the default DB-backed impl; you could swap in a// Redis- or Memcached-backed version by wiring your own callbacks that// match `SessionCreator`/`SessionRevoker`/`SessionChecker`.// 3. The `jti` claim on the JWT is the link between stateless token and// stateful server. The middleware never trusts the JWT alone when a// checker is wired — it confirms the sid is still live.
export { createSessionCallbacks, createSessionsFeature,} from "@cosmicdrift/kumiko-bundled-features/sessions";📄 On GitHub: samples/recipes/session-revocation/src/feature.ts