Fix JWT token uniqueness to prevent revoked-token collision on fast re-login#1086
Merged
Conversation
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>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
jti(UUID) claim to every JWT token created byJWTService.create_access_token(), ensuring tokens are always cryptographically distinct even when issued within the same UTC second for the same userrevoke_token()which was incorrectly readingexpfrom 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 upRoot cause
PyJWTencodes 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)returnedtrue, the server sentWS_AUTH_ERROR, and the client calledlogout()— clearingcurrentUserand 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 logouton fast hardware where the full logout→re-login cycle can complete within one second.Test plan
test/utils/web/test_jwt.py)02-auth.spec.ts: can log back in after logoutnow passes consistently🤖 Generated with Claude Code