Skip to content

feat: JWT mode with access and refresh tokens - #6

Merged
BackStacked merged 1 commit into
mainfrom
claude/graphql-backend-fa2c1c
Oct 14, 2026
Merged

BackStacked merged 1 commit into
mainfrom
claude/graphql-backend-fa2c1c

Conversation

@BackStacked

Copy link
Copy Markdown
Contributor

What and why

Before: login returned one opaque session token, which the client sent on every request until it expired.

After: with createAuth({ …, jwt: { secret } }), login returns two tokens:

  • a short-lived access token, an HS256 JWT carrying sub, org, sid and exp (15 minutes by default);
  • a long-lived refresh token, opaque and single-use.

refresh({ refreshToken }) returns a new pair and spends the old refresh token. If a spent refresh token comes back after a short grace window (15 s, for two tabs refreshing at once), the whole session ends, because that only happens when the token was copied. Without jwt, nothing changes.

The rules stay intact:

  • Only hashes of refresh tokens are stored. A test checks that Redis never holds a raw one.
  • Every request still checks the session and re-reads the user, so logout, revocation and disabling a user apply immediately.
  • verifyAccessToken() checks a JWT's signature without touching storage, for workers or other services.
  • Only HS256 is accepted; the token's own alg is never trusted, so alg: none is refused.
  • Secrets must be at least 32 characters. A list of secrets rotates keys: the first signs, all of them verify.
  • There is no new dependency (node:crypto only).

The api uses this for its new access/refresh flow. Version 1.1.0.

Type

  • Bug fix
  • Feature
  • Test data (ranges, units, prices)
  • Docs
  • Refactor / tooling

Checklist

  • On a branch (not main)
  • npm run typecheck passes
  • npm test: 98 passed, 0 failed. The JWT flows run on both the memory and Redis stores and cover tampering, alg none, foreign secret, rotation, refresh rotation, reuse detection, logout by either token, password change, disabled user, expiry and the no-raw-token check.
  • Docs updated (README with a Mermaid sequence diagram, AGENTS.md rule 6, llms.txt, llms-full.txt)
  • No real patient data, contact details or signatures

🤖 Generated with Claude Code

https://claude.ai/code/session_01BA5Vx77ihDsLiZpyg5duJV


Generated by Claude Code

createAuth({ jwt: { secret, issuer?, accessTtlMs?, reuseGraceMs? } }): login
returns an HS256 access JWT (sub, org, sid) and an opaque single-use refresh
token; refresh() rotates it, and a spent token reused after the grace window
ends the session. Every request still checks the session and re-reads the
user, so logout and disabling apply at once. verifyAccessToken() checks a
JWT without storage. Secret rotation, no dependency (node:crypto). 1.1.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA5Vx77ihDsLiZpyg5duJV
@BackStacked
BackStacked merged commit 3fd91f8 into main Oct 14, 2026
1 check passed
@BackStacked
BackStacked deleted the claude/graphql-backend-fa2c1c branch October 14, 2026 21:48
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.

2 participants