Skip to content

spike(auth): short-lived scoped app tokens (#1069 / #799 P3)#1071

Draft
imajin-jin wants to merge 1 commit into
ima-jin:mainfrom
veteze:spike/1069-app-token
Draft

spike(auth): short-lived scoped app tokens (#1069 / #799 P3)#1071
imajin-jin wants to merge 1 commit into
ima-jin:mainfrom
veteze:spike/1069-app-token

Conversation

@imajin-jin
Copy link
Copy Markdown
Contributor

Spike — short-lived scoped app tokens (#1069 / #799 P3)

Draft / spike. Implements the scoped app-token primitive from the #1069 design so #799's "delegated session token minted from handshake (scoped, time-limited)" (P3) has a concrete shape. Not for merge as-is — see "Not in this spike" below.

What this adds

1. App token sign/verifyapps/kernel/src/lib/auth/jwt.ts

  • createAppToken() / verifyAppToken(). Reuses the existing kernel EdDSA keypair. Tokens are typ: app+jwt (so a session token can't be replayed as an app token), ~10 min TTL, claims: sub=userDid, azp=appDid, scope (space-delimited), aud, attestationId.

2. Mint endpointPOST /auth/api/apps/token

  • App proves possession of its keypair: signs ${appDid}:${attestationId}:${nonce}:${timestamp}; kernel resolves the app DID's public key (createDbResolver) and verifies via the existing verifySignature. Rejects stale/future timestamps (60s window) and short nonces.
  • Then runs the same app.authorized attestation checks as /apps/validate (existence, revokedAt, scope grant), and mints the token. Optional scope narrows to a single granted scope.

3. Stateless verify endpointPOST /auth/api/apps/token/verify

  • Verifies signature + expiry locally (no DB hit), enforces required scope, returns the AppAuthContext shape.

4. requireAppAuth Bearer fast-pathpackages/auth/src/require-app-auth.ts

  • New preferred path: Authorization: Bearer <app-token>verifyBearerAppToken(). Falls back to the legacy X-App-DID + X-App-Authorization (static attestationId) path during migration. No existing behavior removed.

Why this matters for third-party apps

The static X-App-Authorization: <attestationId> bearer is long-lived and replayable. This replaces it with a short-lived, app-key-bound token while keeping the existing consent/scope/revocation system (#799 P1) authoritative. Short TTL bounds the revocation window without a per-call DB lookup.

Verification

  • tsc --noEmit across apps/kernel: 0 errors.
  • No schema changes. No migration needed.

NOT in this spike (follow-ups on #1069)

  • Cookie isolation — narrowing the session cookie from domain=.imajin.ai to host-only, and the staged rollout. This is the other half of Security: app subdomain cookie isolation (scoped tokens, not shared parent-domain cookie) #1069 and the actual security fix; the token is the prerequisite.
  • Fully in-process verifyverifyBearerAppToken currently calls the kernel /token/verify endpoint (stateless, but still a round-trip). Moving jose + the published kernel public key into @imajin/auth removes it entirely.
  • PoP nonce single-use — timestamp window bounds replay; a true nonce cache would harden it further.
  • Wiring apps to actually request/use tokens (first-party dogfood) — pairs with the Epic: RFC-19 — Kernel/Userspace Architecture #799 P1 wiring in the companion PR.

Related: #1069, #799, #244

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 2, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant