{"record":{"id":"59fa14818bd368f0","repo":"hcengineering/platform","slug":"missing-auth-info-59fa14","errorCode":null,"errorMessage":"Missing auth info","messagePattern":"Missing auth info","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"services/payment/pod-payment/src/middleware.ts","lineNumber":62,"sourceCode":"    return\n  }\n  next()\n}\n\nexport const withLoginInfo = (req: RequestWithAuth, res: Response, next: NextFunction): void => {\n  void withLoginInfoAsync(req, res, next)\n}\n\nconst withLoginInfoAsync = async (req: RequestWithAuth, res: Response, next: NextFunction): Promise<void> => {\n  if (req.token === undefined || req.token == null) {\n    res.status(401).json({ message: 'Token error' }).end()\n    return\n  }\n\n  const accountClient = getAccountClient(req.headers.authorization?.split(' ')[1])\n  const loginInfo = await accountClient.getLoginInfoByToken()\n  if (loginInfo == null) {\n    res.status(403).json({ message: 'Missing auth info' }).end()\n    return\n  }\n\n  req.loginInfo = loginInfo\n  next()\n}\n\nexport const withOwner = (req: RequestWithAuth, res: Response, next: NextFunction): void => {\n  void withOwnerAsync(req, res, next)\n}\n\nconst withOwnerAsync = async (req: RequestWithAuth, res: Response, next: NextFunction): Promise<void> => {\n  if (req.token === undefined || req.token == null) {\n    res.status(401).json({ message: 'Token error' }).end()\n    return\n  }\n  if (req.params.workspace != null && req.token.workspace !== req.params.workspace) {\n    res.status(401).json({ message: 'Workspace mismatch' }).end()","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/middleware.ts#L44-L80","documentation":"withLoginInfoAsync rejects with 403 'Missing auth info' when the token exists but the account service returns null from getLoginInfoByToken — i.e. the token is unrecognized, expired, revoked, or the account service is unreachable/misbehaving such that no login info resolves.","triggerScenarios":"Expired or revoked bearer token presented to getLoginInfoByToken; token signed for a different environment/account service; account service down or returning null due to internal error; passing a malformed-but-extractable token string.","commonSituations":"Long-lived clients with expired JWTs that were never refreshed; tokens from a dev account service used against staging payment pod; account service outage or wrong ACCOUNT_SERVICE_URL config; clock skew invalidating freshly issued tokens.","solutions":["Refresh the access token (re-authenticate) and retry with a valid bearer token","Check account service availability and the getAccountClient base URL configuration in this pod","Confirm the token was issued by the same environment's account service (issuer/audience match)","Check for clock skew between pods if tokens are rejected immediately after issuance"],"exampleFix":"// before\n// client retries forever with expired token\nrequestWithSameToken()\n// after\nif (isTokenExpired(token)) token = await refreshToken()\nrequestWithToken(token)","handlingStrategy":"retry","validationCode":"// client-side: check token expiry before calling\nconst payload = JSON.parse(atob(token.split('.')[1]))\nif (payload.exp * 1000 <= Date.now()) {\n  token = await refreshAccessToken() // avoids 403 Missing auth info from a dead token\n}","typeGuard":"function isUsableToken(token: string | undefined): token is string {\n  if (!token) return false\n  try {\n    const { exp } = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString())\n    return typeof exp === 'number' && exp * 1000 > Date.now()\n  } catch { return false }\n}","tryCatchPattern":"try {\n  const res = await callApi()\n} catch (err) {\n  if (err.response?.status === 403 && err.response.data?.message === 'Missing auth info') {\n    await refreshAccessToken();\n    // retry once, then fail; also check account-service health\n  }\n}","preventionTips":["Implement proactive token refresh before expiry (or on 401/403)","Use environment-specific tokens per account service (dev/staging/prod)","Monitor account service health; alert on null getLoginInfoByToken spikes","Clock-sync pods (NTP) to avoid premature token rejection"],"tags":["auth","http-403","expired-token","account-service","token-validation"],"backgroundTag":"jwt-token-expired","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}