{"record":{"id":"65b94e9fb7a887da","repo":"ruvnet/RuView","slug":"invalid-authentication-credentials","errorCode":null,"errorMessage":"Invalid authentication credentials","messagePattern":"Invalid authentication credentials","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"plans/phase2-architecture/api-architecture.md","lineNumber":1571,"sourceCode":"            'exp': expire,\n            'iat': datetime.utcnow(),\n            'scopes': scopes\n        }\n        \n        token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)\n        return token\n    \n    async def verify_token(self, credentials: HTTPAuthorizationCredentials = Security(HTTPBearer())):\n        \"\"\"Verify JWT token\"\"\"\n        token = credentials.credentials\n        \n        try:\n            payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])\n            user_id = payload.get('sub')\n            scopes = payload.get('scopes', [])\n            \n            if user_id is None:\n                raise HTTPException(\n                    status_code=status.HTTP_401_UNAUTHORIZED,\n                    detail=\"Invalid authentication credentials\",\n                    headers={\"WWW-Authenticate\": \"Bearer\"},\n                )\n            \n            return {'user_id': user_id, 'scopes': scopes}\n            \n        except jwt.ExpiredSignatureError:\n            raise HTTPException(\n                status_code=status.HTTP_401_UNAUTHORIZED,\n                detail=\"Token has expired\",\n                headers={\"WWW-Authenticate\": \"Bearer\"},\n            )\n        except jwt.JWTError:\n            raise HTTPException(\n                status_code=status.HTTP_401_UNAUTHORIZED,\n                detail=\"Invalid token\",\n                headers={\"WWW-Authenticate\": \"Bearer\"},","sourceCodeStart":1553,"sourceCodeEnd":1589,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/plans/phase2-architecture/api-architecture.md#L1553-L1589","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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"],"exampleFix":"# before (issuer)\ntoken = jwt.encode({'scopes': ['pose:read']}, secret, algorithm='HS256')\n\n# after (issuer)\ntoken = jwt.encode({'sub': str(user.id), 'scopes': ['pose:read']}, secret, algorithm='HS256')","handlingStrategy":"try-catch","validationCode":"import jwt\nunverified = jwt.decode(token, options={'verify_signature': False})\nassert unverified.get('sub'), 'token lacks sub claim; it will be rejected with 401'","typeGuard":null,"tryCatchPattern":"from fastapi import HTTPException\ntry:\n    await client.get('/api/pose', headers={'Authorization': f'Bearer {token}'})\nexcept HTTPException as e:\n    if e.status_code == 401 and e.detail == 'Invalid authentication credentials':\n        token = await obtain_new_token()  # token verified but had no 'sub'\n        raise","preventionTips":["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"],"tags":["python","fastapi","jwt","authentication","http-401"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}