Token Versioning
Token versioning is a mechanism that allows immediate invalidation of all active access tokens for a user without maintaining a token blacklist.
Problem
JWT access tokens are stateless — once issued, they are valid until they expire. Revoking a refresh token in the database does not affect already-issued access tokens. This means that after logout-all, a user's access token remains valid until its natural expiry (up to 15 minutes).
Solution
Each user has a token_version integer column (default: 1). This value is embedded in every access token at login time. On every authenticated request, the version in the token is compared against the version in the database.
token.token_version == user.token_version → valid ✅
token.token_version != user.token_version → 401 Unauthorized ❌
Invalidation
When POST /auth/logout-all is called:
1. user.token_version increments: 1 → 2
2. All refresh tokens are revoked in the database
3. All existing access tokens now carry version 1
4. version 1 != version 2 → all tokens immediately invalid
Normal Login (Multiple Devices)
token_version is only incremented on logout-all — not on login. This means multiple simultaneous sessions work correctly:
Device A — login → token_version: 1 → valid ✅
Device B — login → token_version: 1 → valid ✅
logout-all called:
→ token_version: 1 → 2
Device A → version 1 != 2 → 401 ❌
Device B → version 1 != 2 → 401 ❌
Device A logs in again → token_version: 2 → valid ✅
Implementation
users table
Access token payload
Validation in dependencies.py
token_version = payload.get("token_version")
if token_version is None or token_version != user.token_version:
raise InvalidTokenException()
Increment on logout-all
def logout_all(self, user_id) -> None:
user = self.user_repo.get_by_id_with_roles(user_id)
if user:
self.user_repo.increment_token_version(user)
self.token_repo.revoke_all_for_user(user_id)
Trade-offs
| Aspect | Token Blacklist | Token Versioning |
|---|---|---|
| Granularity | Per token | Per user (all tokens) |
| DB queries | 1 extra per request | 0 extra (user already loaded) |
| Invalidation | Immediate, per token | Immediate, all tokens |
| Complexity | Requires cleanup job | Simple integer increment |
Token versioning is the right choice when the requirement is "logout from all devices" rather than "logout from this specific device". For single-device logout, simply revoking the refresh token and waiting for the access token to expire naturally is sufficient.