Skip to content

feat(auth): revocation-aware token introspection endpoint (TODO 4) - #117

Closed
lopugit wants to merge 2 commits into
mainfrom
claude/todo4-token-introspection-s3
Closed

feat(auth): revocation-aware token introspection endpoint (TODO 4)#117
lopugit wants to merge 2 commits into
mainfrom
claude/todo4-token-introspection-s3

Conversation

@lopugit

@lopugit lopugit commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Fixes TODO 4 — Add revocation-aware token introspection for external platforms.

What

Adds POST /api/v1/auth/introspect. /api/v1/auth/jwks already lets external platforms verify a token's signature, issuer, and expiry offline — introspection adds the online half JWKS structurally cannot answer: is the session behind the token still live?

  • remix/app/api/utils/auth/introspection.tsintrospectToken: re-verifies the token via the shared verifyJwt, then checks the backing session in Mongo through the same session→user path getCurrentUser uses (resolveSessionUser), so introspection can never disagree with normal auth.
  • remix/app/routes/api/v1/auth/introspect/_introspect.tsx — the route: 16 KiB body cap, per-IP rate limit, private no-store.
  • Registered in the Nitro dispatcher; auth.introspect rate rule (120/min/IP); API doc entry; two apiTests.

Response shape

  • Live account session → { active: true, sub, jti, exp, iat, iss, purpose: 'session', username }
  • Live app token ("Login with Thingtime" grant) → purpose: 'app', client_id (these are the grants the general auth path rejects on purpose — introspection reports them so integrations can poll revocation without holding full account credentials)
  • Invalid / expired / revoked → identical { active: false } (no oracle for why)

Anonymous like JWKS — a token holder can already probe liveness against any authed route — but rate limited so it can't be used for bulk token scanning.

Verification (live against local Nitro)

case result
fresh session token active:true + sub/jti/exp/iat/purpose:session/username
same token after logout active:false ✅ (revocation works)
missing / blank token 400 validation error
garbage / unsigned JWT active:false, no identity fields

Also annotates TODO 4 in TODO/TODO.md.

Session 3 of 10 parallel todo sessions (previous: #94, #102, #105, #112); claim branch pushed before work started.

🤖 Generated with Claude Code

/api/v1/auth/jwks lets external platforms verify a token's signature,
issuer, and expiry offline, but can't answer the one question offline
verification never can: is the session behind the token still live?

Adds POST /api/v1/auth/introspect (introspection.ts + route). It
re-verifies the token through the shared verifier, then checks the
backing session in Mongo via the same session->user path getCurrentUser
uses, so introspection can never disagree with it. Returns
{active, sub, jti, exp, iat, iss} plus purpose: app tokens report
purpose:'app'+client_id (the "Login with Thingtime" grants the general
auth path rejects on purpose), account sessions report
purpose:'session'+username. Every invalid/expired/revoked token returns
an identical {active:false} so the endpoint is not an oracle for why a
token stopped working.

Anonymous like jwks (a token holder can already probe liveness against
any authed route) but rate limited per IP (auth.introspect, 120/min) so
it can't be used for bulk token scanning. Registered in the Nitro
dispatcher; API doc + two apiTests added.

Verified live against local Nitro: a fresh session token introspects
active:true with its identity, then active:false after logout revokes
the session; missing/blank token -> 400; garbage/unsigned token ->
active:false with no identity fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
thingtime Ready Ready Preview, Comment Jul 26, 2026 12:38pm

Request Review

@lopugit

lopugit commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

Consolidating duplicate TODO-4 PRs: #116 is the single winner — its introspection path deliberately reports purpose:'app' sessions (the Login-with-Thingtime tokens external platforms actually hold), which this PR's resolution path would default-deny as active:false. See the consolidation note on #116.

@lopugit lopugit closed this Jul 30, 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