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

  1. Obtain a fresh token via the login/token endpoint and retry the request
  2. Implement a refresh-token flow so clients silently renew before exp
  3. Verify clocks: ensure both issuer and API server run NTP; skew makes valid tokens appear expired
  4. 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

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


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/6170322521a2942b. Report an issue: GitHub.