ruvnet/RuView · error · HTTPException
Invalid authentication credentials
Error message
Invalid authentication credentials
What it means
HTTP 401 raised in verify_token when jwt.decode succeeds (correct signature and algorithm) but the payload has no 'sub' claim. The token is cryptographically valid but lacks the user-id subject this auth scheme requires, so it cannot be mapped to a user. Distinguished from signature/expiry errors, which hit the dedicated handlers below.
Source
Thrown at plans/phase2-architecture/api-architecture.md:1571
'exp': expire,
'iat': datetime.utcnow(),
'scopes': scopes
}
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
return token
async def verify_token(self, credentials: HTTPAuthorizationCredentials = Security(HTTPBearer())):
"""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"},View on GitHub (pinned to 4685618388)
Solutions
- Ensure the token-issuing code includes the subject: jwt.encode({'sub': user_id, 'scopes': [...], ...})
- Decode the token client-side (e.g. jwt.io or jwt.decode(token, options={'verify_signature': False})) and confirm 'sub' is present in the payload
- If the token comes from another service, obtain a token from this API's own /token endpoint instead
- Log the decoded payload (minus secrets) at debug level when this 401 fires to see exactly which claims arrived
Example fix
# before (issuer)
token = jwt.encode({'scopes': ['pose:read']}, secret, algorithm='HS256')
# after (issuer)
token = jwt.encode({'sub': str(user.id), 'scopes': ['pose:read']}, secret, algorithm='HS256') Defensive patterns
Strategy: try-catch
Validate before calling
import jwt
unverified = jwt.decode(token, options={'verify_signature': False})
assert unverified.get('sub'), 'token lacks sub claim; it will be rejected with 401' Try / catch
from fastapi import HTTPException
try:
await client.get('/api/pose', headers={'Authorization': f'Bearer {token}'})
except HTTPException as e:
if e.status_code == 401 and e.detail == 'Invalid authentication credentials':
token = await obtain_new_token() # token verified but had no 'sub'
raise Prevention
- Always include 'sub' (and 'scopes') when minting tokens at the /token endpoint
- Add a unit test that decodes a freshly issued token and asserts the 'sub' claim exists
- Do not reuse tokens from unrelated services -- each API expects its own claim schema
When it happens
Trigger: Presenting a Bearer token that decodes and verifies against secret_key but whose payload omits 'sub': tokens minted by another issuer/service in your org, hand-built JWTs, or an old token version that stopped including the subject claim.
Common situations: Reusing an ID token or service-to-service token from a different auth service on this API; issuer recently changed its token template and dropped 'sub'; tests that craft payloads manually with only 'scopes'.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Token has expired
- 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/65b94e9fb7a887da.
Report an issue: GitHub.