Skip to content

Fix JWT token uniqueness to prevent revoked-token collision on fast re-login#1086

Merged
Tim020 merged 3 commits into
devfrom
fix/jwt-token-uniqueness
May 25, 2026
Merged

Fix JWT token uniqueness to prevent revoked-token collision on fast re-login#1086
Tim020 merged 3 commits into
devfrom
fix/jwt-token-uniqueness

Conversation

@Tim020
Copy link
Copy Markdown
Contributor

@Tim020 Tim020 commented May 25, 2026

Summary

  • Adds a unique jti (UUID) claim to every JWT token created by JWTService.create_access_token(), ensuring tokens are always cryptographically distinct even when issued within the same UTC second for the same user
  • Fixes revoke_token() which was incorrectly reading exp from the JWT header (where it is absent → always -1) instead of the payload, causing revoked tokens to accumulate in memory forever and never be cleaned up

Root cause

PyJWT encodes tokens deterministically: same payload + same secret + same algorithm = same token string. Without a unique-per-token field, two login calls for the same user within the same UTC second produced identical tokens. When a user logged out (revoking token T1) and immediately re-logged in (producing T2 = T1), is_token_revoked(T2) returned true, the server sent WS_AUTH_ERROR, and the client called logout() — clearing currentUser and making the UI show "Login" instead of the username.

This manifested as a flaky E2E test failure in 02-auth.spec.ts: can log back in after logout on fast hardware where the full logout→re-login cycle can complete within one second.

Test plan

  • All 4 existing JWT unit tests pass (test/utils/web/test_jwt.py)
  • Full 603-test backend suite passes
  • Full 148-test Playwright E2E suite (Chromium) passes locally — 02-auth.spec.ts: can log back in after logout now passes consistently

🤖 Generated with Claude Code

PyJWT tokens created within the same UTC second for the same user
produce identical byte strings (same user_id, token_version, iat, exp).
When a logout revoked token T1 and a subsequent re-login created T2
within the same second, T2 === T1 causing is_token_revoked() to return
true and trigger WS_AUTH_ERROR → logout on the newly authenticated
session.

Fix: add a unique jti (UUID) claim to every created token so tokens are
always distinct regardless of timing.

Also fix revoke_token() which incorrectly read exp from the JWT
*header* (where it doesn't exist → always -1), preventing expired
tokens from ever being purged from the in-memory revocation dict.
Now reads exp from the payload via an unverified decode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added small-diff Small pull request server Pull requests changing back end code labels May 25, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

Client V3 Test Results

23 tests  ±0   23 ✅ ±0   0s ⏱️ ±0s
 2 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit ba3cf43. ± Comparison against base commit a21d4ae.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

Client Test Results

128 tests  ±0   128 ✅ ±0   0s ⏱️ ±0s
  6 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit ba3cf43. ± Comparison against base commit a21d4ae.

♻️ This comment has been updated with latest results.

@Tim020 Tim020 enabled auto-merge (squash) May 25, 2026 21:05
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

Python Test Results

  1 files  ±0    1 suites  ±0   1m 33s ⏱️ +4s
603 tests ±0  603 ✅ ±0  0 💤 ±0  0 ❌ ±0 
608 runs  ±0  608 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit ba3cf43. ± Comparison against base commit a21d4ae.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

Playwright E2E Results (chromium)

148 tests  ±0   148 ✅ ±0   1m 25s ⏱️ +6s
 14 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit ba3cf43. ± Comparison against base commit a21d4ae.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

Playwright E2E Results (firefox)

148 tests  ±0   148 ✅ ±0   1m 26s ⏱️ +5s
 14 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit ba3cf43. ± Comparison against base commit a21d4ae.

♻️ This comment has been updated with latest results.

@sonarqubecloud
Copy link
Copy Markdown

@Tim020 Tim020 merged commit 7aa6a13 into dev May 25, 2026
35 checks passed
@Tim020 Tim020 deleted the fix/jwt-token-uniqueness branch May 25, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client-v3 server Pull requests changing back end code small-diff Small pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant