{"record":{"id":"4a4a441c9b2bd26a","repo":"bytedance/deer-flow","slug":"token-revoked-password-changed","errorCode":null,"errorMessage":"Token revoked (password changed)","messagePattern":"Token revoked \\(password changed\\)","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"backend/app/gateway/langgraph_auth.py","lineNumber":97,"sourceCode":"            status_code=401,\n            detail=\"Not authenticated\",\n        )\n\n    payload = decode_token(token)\n    if isinstance(payload, TokenError):\n        raise Auth.exceptions.HTTPException(\n            status_code=401,\n            detail=\"Invalid token\",\n        )\n\n    user = await get_local_provider().get_user(payload.sub)\n    if user is None:\n        raise Auth.exceptions.HTTPException(\n            status_code=401,\n            detail=\"User not found\",\n        )\n    if user.token_version != payload.ver:\n        raise Auth.exceptions.HTTPException(\n            status_code=401,\n            detail=\"Token revoked (password changed)\",\n        )\n\n    return payload.sub\n\n\n@auth.on\nasync def add_owner_filter(ctx: Auth.types.AuthContext, value: dict):\n    \"\"\"Inject user_id metadata on writes; filter by user_id on reads.\n\n    Gateway stores thread ownership as ``metadata.user_id``.\n    This handler ensures LangGraph Server enforces the same isolation.\n    \"\"\"\n    # On create/update: stamp user_id into metadata\n    metadata = value.setdefault(\"metadata\", {})\n    metadata[\"user_id\"] = ctx.user.identity\n","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/langgraph_auth.py#L79-L115","documentation":"The token decoded and the user exists, but `user.token_version != payload.ver`: the password was changed (or sessions forcibly invalidated) after this token was issued, bumping the stored token_version. The token is deliberately rejected with HTTP 401 'Token revoked (password changed)'.","triggerScenarios":"Changing the account password while another browser/tab/session holds an older cookie; an admin force-logout that bumps token_version; any security event that rotates token_version.","commonSituations":"Password reset from a different device leaving stale sessions on others; bulk session invalidation after a security incident; tests reusing fixtures after password updates.","solutions":["Re-authenticate: log in again with the current password to get a token carrying the new token_version","If you administer the server and want sessions preserved across a password change, bump token_version only when forced logout is intended","For automated clients, catch this specific 401 and trigger the login flow instead of retrying"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await call();\n} catch (e) {\n  if (e.status === 401 && /Token revoked/.test(e.detail)) {\n    await login();       // password changed elsewhere; get new token_version\n    return await call();\n  }\n  throw e;\n}","preventionTips":["After any password change, immediately re-login in all open sessions","Automated clients should catch 'Token revoked' specifically and re-authenticate rather than retry","Never cache auth tokens across password-change events"],"tags":["auth","http-401","password-change","session-invalidation"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}