ruvnet/RuView · error · HTTPException

Invalid token

Error message

Invalid token

What it means

HTTP 401 raised from the catch-all jwt.JWTError handler in verify_token. It means the token failed cryptographic validation outright: bad signature (secret_key or algorithm mismatch), malformed token, or a decode error -- as opposed to a missing 'sub' or an expired signature, which have their own handlers.

Source

Thrown at plans/phase2-architecture/api-architecture.md:1586

            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:
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail=f"Not enough permissions. Required scope: {scope}"
                    )
            
            return token_data

View on GitHub (pinned to 4685618388)

Solutions

  1. Align secret_key and algorithm between the token issuer and this verifier (check env/config on both sides)
  2. Confirm the client sends 'Authorization: Bearer <jwt>' with the full three-part token and no whitespace corruption
  3. Re-login to get a token minted by the same authority the server trusts
  4. If multiple issuers are legitimate, extend verify_token to try per-issuer keys/algorithms explicitly

Example fix

# before
auth = JWTAuth(secret_key=getenv('JWT_SECRET'), algorithm='HS256')  # issuer signs RS256 elsewhere

# after
auth = JWTAuth(secret_key=getenv('JWT_SECRET'), algorithm='HS256')
# issuer must sign with the same secret+algorithm:
token = jwt.encode({'sub': user_id, 'scopes': scopes, 'exp': exp}, getenv('JWT_SECRET'), algorithm='HS256')
Defensive patterns

Strategy: try-catch

Validate before calling

import jwt
token = token.removeprefix('Bearer ').strip()
jwt.decode(token, secret_key, algorithms=['HS256'])  # local canary: fails fast with the same class of error

Try / catch

try:
    result = await call_api(token)
except HTTPException as e:
    if e.status_code == 401 and e.detail == 'Invalid token':
        token = await login_again()  # signature/algorithm mismatch; get a token from this authority
    else:
        raise

Prevention

When it happens

Trigger: Token signed with a different secret than self.secret_key; token algorithm differs from self.algorithm (e.g. token is RS256 but server expects HS256); corrupted/truncated Authorization header; a non-JWT string passed after the Bearer prefix.

Common situations: Secret rotated on the issuer but not the API (or vice versa); environment drift between dev/prod secrets; alg-confusion tokens from a different IdP; proxy or client mangling the header.

Understand the failure class

Related errors


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