{"record":{"id":"b2d758e8c207b704","repo":"calcom/cal.diy","slug":"permissionsguard-no-oauth-client-found-for-acces","errorCode":null,"errorMessage":"PermissionsGuard - no oAuth client found for access token=${accessToken}","messagePattern":"PermissionsGuard - no oAuth client found for access token=(.+?)","errorType":"http","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"apps/api/v2/src/modules/auth/guards/permissions/permissions.guard.ts","lineNumber":79,"sourceCode":"        `PermissionsGuard - oAuth client with id=${\n          oAuthClient.id\n        } does not have the required permissions=${requiredPermissions\n          .map((permission) => this.oAuthClientsOutputService.transformOAuthClientPermission(permission))\n          .join(\n            \", \"\n          )}. Go to platform dashboard settings and add the required permissions to the oAuth client.`\n      );\n    }\n\n    return true;\n  }\n\n  async getOAuthClientByAccessToken(\n    accessToken: string\n  ): Promise<Pick<PlatformOAuthClient, \"id\" | \"permissions\">> {\n    const oAuthClient = await this.tokensRepository.getAccessTokenClient(accessToken);\n    if (!oAuthClient) {\n      throw new ForbiddenException(\n        `PermissionsGuard - no oAuth client found for access token=${accessToken}`\n      );\n    }\n    return oAuthClient;\n  }\n\n  async getOAuthClientById(id: string): Promise<Pick<PlatformOAuthClient, \"id\" | \"permissions\">> {\n    const oAuthClient = await this.oAuthClientRepository.getOAuthClient(id);\n    if (!oAuthClient) {\n      throw new ForbiddenException(`PermissionsGuard - no oAuth client found for client id=${id}`);\n    }\n    return oAuthClient;\n  }\n\n  getDecodedThirdPartyAccessToken(bearerToken: string) {\n    return this.tokensService.getDecodedThirdPartyAccessToken(bearerToken);\n  }\n}","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/auth/guards/permissions/permissions.guard.ts#L61-L97","documentation":"ForbiddenException from PermissionsGuard.getOAuthClientByAccessToken when tokensRepository.getAccessTokenClient(accessToken) returns null — i.e. the supplied Bearer access token does not match any row in the platform access-tokens store. The token itself is echoed back in the message (note: this leaks the credential into logs/responses — a hygiene concern).","triggerScenarios":"Calling a PermissionsGuard-protected endpoint with a Bearer token that is expired, revoked, malformed, or issued by a different system (e.g. a NextAuth session token that wasn't short-circuited, or a third-party token that getDecodedThirdPartyAccessToken didn't recognize).","commonSituations":"Token expired (access tokens are short-lived); token revoked from the dashboard; copy/paste truncation; using a refresh token where an access token is required; environment drift between issuing and verifying API; clock skew causing premature expiry.","solutions":["Refresh the access token using the OAuth2 refresh_token grant, then retry.","Confirm the token was issued by this platform's OAuth server and not by a third-party IdP.","If revoked, re-issue via the authorize flow.","As a platform maintainer: do NOT echo the raw accessToken back in the error message — log only a truncated/hash identifier to avoid credential leakage."],"exampleFix":"// before — leaks the credential\nthrow new ForbiddenException(\n  `PermissionsGuard - no oAuth client found for access token=${accessToken}`\n);\n\n// after — redact\nthrow new ForbiddenException(\n  `PermissionsGuard - no oAuth client found for access token=${accessToken.slice(0, 6)}…`\n);","handlingStrategy":"retry","validationCode":"// Decode JWT exp client-side to catch expiry before sending\nfunction isLikelyExpired(token: string): boolean {\n  try {\n    const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());\n    return typeof payload.exp === 'number' && payload.exp * 1000 < Date.now();\n  } catch { return true; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await client.get('/v2/protected');\n} catch (e) {\n  if (e.status === 403 && /no oAuth client found for access token/.test(e.message)) {\n    await refreshToken(); // refresh_token grant, then retry once\n    return client.get('/v2/protected');\n  }\n  throw e;\n}","preventionTips":["Refresh access tokens before they expire.","Never log/echo the raw access token — the current server message leaks it; file an issue to redact.","Distinguish access tokens from refresh tokens and from NextAuth session tokens."],"tags":["auth","oauth","access-token","security","credential-leak","guard"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}