calcom/cal.diy · error · OAuth2HttpException
${reason ?? err.message}
Error message
${reason ?? err.message} What it means
Thrown by handleTokenError when the token-exchange error is an ErrorWithCode. The OAuth2 token endpoint (/v2/oauth/{clientId}/token) returns a JSON body {error: err.message, error_description: reason ?? err.message} with the HTTP status derived from the error code via getHttpStatusCode. error_description falls back to err.message when no reason is present in err.data.
Source
Thrown at apps/api/v2/src/modules/auth/oauth2/services/oauth2-error.service.ts:44
throw new OAuth2HttpException(
{
error: err.message,
error_description: reason,
},
statusCode
);
}
}
const errorRedirectUrl = this.oAuthService.buildErrorRedirectUrl(redirectUri, err, state);
throw new OAuth2RedirectException(errorRedirectUrl);
}
handleTokenError(err: unknown): never {
if (err instanceof ErrorWithCode) {
const statusCode = getHttpStatusCode(err);
const reason = err.data?.["reason"] as string | undefined;
throw new OAuth2HttpException(
{
error: err.message,
error_description: reason ?? err.message,
},
statusCode
);
}
this.logger.error(err);
throw new OAuth2HttpException(
{
error: "server_error",
error_description: "An unexpected error occurred",
},
500
);
}
handleClientError(err: unknown, fallbackMessage: string): never {View on GitHub (pinned to 176037d0af)
Solutions
- Read the error field in the JSON response — it is the OAuth2 error code (e.g., invalid_grant, invalid_client) that pinpoints the failure.
- If invalid_grant: generate a fresh authorization code via the /authorize flow rather than retrying the same code.
- If invalid_client: verify client_id and client_secret against the platform settings page for the correct environment.
- For PKCE flows, ensure the code_verifier sent to /token matches the code_challenge sent to /authorize (both S256 and the same random string).
Example fix
// before — reusing a code after a failed attempt
const tokenRes = await fetch(tokenUrl, { method: 'POST', body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri, client_id, client_secret }) });
// retry with the same `code` → invalid_grant
// after — on failure, restart the authorize flow to get a new code
if (!tokenRes.ok) { window.location.href = authorizeUrl; return; } Defensive patterns
Strategy: try-catch
Validate before calling
// Validate PKCE verifier against the stored challenge before calling /token
function validatePkce(verifier: string, challenge: string) {
if (!verifier || verifier.length < 43) throw new Error('code_verifier too short');
const expected = base64url(sha256(verifier));
if (expected !== challenge) throw new Error('PKCE verifier/challenge mismatch');
}
// Validate grant_type is one of the supported values
const GRANT_TYPES = new Set(['authorization_code','refresh_token','client_credentials']);
if (!GRANT_TYPES.has(grantType)) throw new Error(`unsupported grant_type: ${grantType}`); Type guard
interface OAuth2TokenErrorBody { error: string; error_description?: string; }
function isOAuth2TokenErrorBody(v: unknown): v is OAuth2TokenErrorBody {
return typeof v === 'object' && v !== null && typeof (v as any).error === 'string';
} Try / catch
try {
const token = await exchangeCodeForToken(code, verifier);
} catch (err) {
if (err.response && isOAuth2TokenErrorBody(err.response.data)) {
const { error, error_description } = err.response.data;
if (error === 'invalid_grant') { redirectToAuthorize(); return; }
if (error === 'invalid_client') { refreshClientSecret(); return; }
}
throw err;
} Prevention
- Treat authorization codes as single-use; never replay a code after any outcome.
- Store the PKCE verifier alongside the state so the /token call always has the matching pair.
- Map known error codes (invalid_grant, invalid_client) to specific recovery flows rather than generic retry.
When it happens
Trigger: POST to /v2/oauth/{clientId}/token with an invalid or expired authorization code, an invalid grant_type, a missing or wrong code_verifier for PKCE, a client_secret mismatch, or an already-consumed code. Any ErrorWithCode raised by the grant handling surfaces here.
Common situations: Reusing an authorization code after it was already exchanged (codes are single-use); clock drift causing the code to appear expired; PKCE verifier mismatch because the S256 challenge was generated with a different secret; wrong client_secret copied from another client.
Related errors
- ${reason}
- CustomThrottlerGuard - Invalid API Key
- ApiKeysService - This endpoint can only be accessed using an
- ApiKeysService - No API key provided
- ApiKeysService - provided api key is not valid.
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/2d305cf4a3d467ed.
Report an issue: GitHub.