Handling Sessions with JWTs
Jan 15, 2025
Stateless JWTs simplify authentication but risk valid tokens post-logout. We can fix this by embedding expiration timestamps and validate server-side.
Authentication can feel overwhelming - there are countless libraries and services promising secure solutions. But for simple web apps, JSON Web Tokens (JWTs) gets the job done. JWTs work like digital ID cards: the server creates an encoded string (using cryptographic algorithms) that clients store and send back as a Bearer token in subsequent requests. These tokens contain verified information (like user IDs) that server can quickly validate without constant database checks.
The Stateless Challenge
JWTs are stateless by design - meaning the server doesn’t track their status after creation. Imagine a scenario where a user logs out on their device, but their JWT remains valid until expiration. Without additional safeguards, this creates a security gap where “logged out” users could theoretically keep accessing services.
Solution - Using Timestamps in JWT
One way to get around is by adding time limits directly into the tokens. When generating a JWT, include both the user ID and an expiration timestamp. Now the server does two checks during authentication:
- Is the signature valid?
- Is the token’s expiration time still in the future?
This simple strategy mimics session management without complex server-side tracking.
import jwt
from datetime import datetime, timedelta, timezone
from time import sleep
SECRET_KEY = "SUPERSECRETKEY"
ALGORITHM = "HS256"
def create_access_token(username: str, expires_in: int = 5):
now = datetime.now(tz=timezone.utc)
payload = {
"sub": username,
"iat": now,
"exp": now + timedelta(seconds=expires_in)
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def validate_token(token: str, leeway: int = 0):
try:
return jwt.decode(
token,
SECRET_KEY,
algorithms=[ALGORITHM],
leeway=leeway
)
except jwt.ExpiredSignatureError:
return "Token expired"
except Exception as e:
return f"Invalid token: {e}"
print("=== Valid Token Test ===")
valid_token = create_short_lived_token("johndoe", expires_in=10)
print("Validation result:", validate_token(valid_token))
print("\n=== Expired Token Test ===")
expired_token = create_short_lived_token("johndoe", expires_in=2)
sleep(3) # Wait for token to expire
print("Without leeway:", validate_token(expired_token))
print("With 5s leeway:", validate_token(expired_token, leeway=5))
While expiration timestamps work for simple apps, production systems often pair short-lived tokens with refresh tokens (long-lived but revocable). This hybrid approach reduces frequent re-authentication while limiting exposure if a token is compromised. Even these advanced systems still rely on the core concept: time-bound validation to balance security and usability.