ruvnet/RuView · error · HTTPException
Token has expired
Error message
Token has expired
What it means
HTTP 401 raised when jwt.decode throws jwt.ExpiredSignatureError, i.e. the token's 'exp' claim is in the past. The signature was valid; the credential simply aged out. The response includes WWW-Authenticate: Bearer per RFC 6750 so clients know to re-authenticate.
Source
Thrown at plans/phase2-architecture/api-architecture.md:1580
"""Verify JWT token"""
token = credentials.credentials
try:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
user_id = payload.get('sub')
scopes = payload.get('scopes', [])
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return {'user_id': user_id, 'scopes': scopes}
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token has expired",
headers={"WWW-Authenticate": "Bearer"},
)
except jwt.JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"},
)
def require_scopes(self, required_scopes: List[str]):
"""Decorator to require specific scopes"""
async def scope_checker(token_data: dict = Depends(self.verify_token)):
user_scopes = token_data.get('scopes', [])
for scope in required_scopes:
if scope not in user_scopes:View on GitHub (pinned to 4685618388)
Solutions
- Obtain a fresh token via the login/token endpoint and retry the request
- Implement a refresh-token flow so clients silently renew before exp
- Verify clocks: ensure both issuer and API server run NTP; skew makes valid tokens appear expired
- If a slightly longer session is acceptable, increase the exp delta at token creation (weigh security)
Example fix
# before
async def call_api():
return await client.get('/pose', headers=bearer_headers(old_token))
# after
async def call_api():
resp = await client.get('/pose', headers=bearer_headers(old_token))
if resp.status_code == 401 and resp.json()['detail'] == 'Token has expired':
old_token = await refresh_or_login()
resp = await client.get('/pose', headers=bearer_headers(old_token))
return resp Defensive patterns
Strategy: try-catch
Validate before calling
import jwt, time
unverified = jwt.decode(token, options={'verify_signature': False})
if unverified.get('exp', 0) <= time.time() + 30:
token = await refresh_or_login() # renew with margin instead of letting the server 401 Try / catch
try:
result = await call_api(token)
except HTTPException as e:
if e.status_code == 401 and e.detail == 'Token has expired':
token = await refresh_or_login()
result = await call_api(token) # single retry with the fresh token
else:
raise Prevention
- Refresh tokens slightly before exp instead of waiting for the 401
- Run NTP on issuers and API servers to avoid skew-induced expiries
- Treat 'Token has expired' as a routine client-side event (refresh + retry once), not a fatal error
When it happens
Trigger: Sending any Bearer token whose exp timestamp is earlier than the server's current time: sessions older than the token TTL, tokens cached in localStorage beyond expiry, or clock skew where the client machine (or server) has a wrong clock.
Common situations: Long-running SPAs or notebooks holding tokens past their TTL; NTP drift between token issuer and API server; short-lived access tokens used without a refresh flow.
Related errors
- Invalid authentication credentials
- Invalid token
- JWT authentication is not configured. In development mode, e
- Authentication required
- Missing or invalid Authorization header
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/6170322521a2942b.
Report an issue: GitHub.