calcom/cal.diy · error · BadRequestException
Invalid refresh token
Error message
Invalid refresh token
What it means
Thrown by refreshToken after the client+secret matched, when oauthClient.refreshToken[0] is falsy. The refresh token row is absent — meaning the refresh token was revoked, deleted, already rotated (old one removed), or never existed for this client. BadRequestException (HTTP 400). The user must re-authorize to obtain a new grant.
Source
Thrown at apps/api/v2/src/modules/oauth-clients/services/oauth-flow.service.ts:154
refreshTokenExpiresAt: refreshTokenExpiresAt.valueOf(),
};
}
async refreshToken(clientId: string, clientSecret: string, tokenSecret: string): Promise<KeysDto> {
const oauthClient = await this.oAuthClientRepository.getOAuthClientWithRefreshSecret(
clientId,
clientSecret,
tokenSecret
);
if (!oauthClient) {
throw new BadRequestException("Invalid OAuthClient credentials.");
}
const currentRefreshToken = oauthClient.refreshToken[0];
if (!currentRefreshToken) {
throw new BadRequestException("Invalid refresh token");
}
const { accessToken, refreshToken } = await this.tokensRepository.refreshOAuthTokens(
clientId,
currentRefreshToken.secret,
currentRefreshToken.userId
);
return {
accessToken: accessToken.secret,
accessTokenExpiresAt: accessToken.expiresAt.valueOf(),
refreshToken: refreshToken.secret,
refreshTokenExpiresAt: refreshToken.expiresAt.valueOf(),
};
}
private _generateActKey(accessToken: string) {
return `act_${accessToken}`;View on GitHub (pinned to 176037d0af)
Solutions
- Re-run the full OAuth authorize+exchange flow to obtain a fresh refresh token.
- If using refresh-token rotation, never reuse a refresh token that was already exchanged.
- Store only the latest refresh token after each refresh.
- Surface a 're-login required' prompt to the user when refresh fails this way.
Example fix
// before
await oauthFlow.refreshToken(clientId, secret, oldRefreshToken); // 400
// after — re-authorize to get a new grant
const code = await authorize(userId, clientId, redirectUri);
const { accessToken, refreshToken } = await oauthFlow.exchangeAuthorizationToken(code, clientId, secret);
await oauthFlow.refreshToken(clientId, secret, refreshToken); Defensive patterns
Strategy: fallback
Validate before calling
// Track the latest refresh token; detect when re-authorize is required
function canAttemptRefresh(token: { refreshToken: string } | null | undefined): token is { refreshToken: string } {
return Boolean(token && typeof token.refreshToken === 'string' && token.refreshToken.length > 0);
}
if (!canAttemptRefresh(stored)) {
throw new Error('No usable refresh token; user must re-authorize');
} Type guard
function hasRefreshToken(value: unknown): value is { refreshToken: string } {
return typeof value === 'object' && value !== null && typeof (value as any).refreshToken === 'string';
} Try / catch
try {
return await oauthFlow.refreshToken(clientId, clientSecret, refreshToken);
} catch (e) {
if (e instanceof BadRequestException && /refresh token/i.test(e.message)) {
// refresh token revoked/rotated — fall back to full re-authorization
const code = await reAuthorize(userId, clientId, redirectUri);
return await oauthFlow.exchangeAuthorizationToken(code, clientId, clientSecret);
}
throw e;
} Prevention
- Under refresh-token rotation, never reuse an already-used refresh token.
- Store only the latest refresh token after each refresh.
- Prompt re-login when refresh fails with this error.
When it happens
Trigger: POST /refresh with a refresh token that has been revoked or rotated out; a refresh token from a different client; a refresh token whose row was deleted.
Common situations: Refresh-token rotation policies that delete the old token; an admin revoked the session; the refresh token expired and was cleaned up; reusing a refresh token after it was already used under one-time-use rotation.
Related errors
- Feishu Calendar refresh token expired
- PermissionsGuard - no authentication provided. Provide eithe
- PermissionsGuard - oAuth client with id=${oAuthClient.id} do
- PermissionsGuard - no oAuth client found for access token=${
- PermissionsGuard - no oAuth client found for client id=${id}
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/7ce38156a06a9629.
Report an issue: GitHub.