calcom/cal.diy · error · UnauthorizedException
Invalid Access token.
Error message
Invalid Access token.
What it means
Thrown by ConferencingService.connectOauthApps when tokensRepository.getAccessTokenOwnerId returns null for the access token embedded in the decoded callback state. The token either does not exist in the DB, is expired, or is malformed. Returns HTTP 401 via UnauthorizedException. This is the user-binding step of the OAuth callback — it establishes which user the new credential belongs to.
Source
Thrown at apps/api/v2/src/modules/conferencing/services/conferencing.service.ts:63
async connectUserNonOauthApp(app: string, userId: number) {
switch (app) {
case GOOGLE_MEET:
const credential = await this.googleMeetService.connectGoogleMeetToUser(userId);
return credential;
default:
throw new BadRequestException("Invalid conferencing app. Available apps: GOOGLE_MEET.");
}
}
async connectOauthApps(
app: string,
code: string,
decodedCallbackState: OAuthCallbackState,
teamId?: number
) {
const userId = await this.tokensRepository.getAccessTokenOwnerId(decodedCallbackState.accessToken);
if (!userId) {
throw new UnauthorizedException("Invalid Access token.");
}
switch (app) {
case ZOOM:
return await this.zoomVideoService.connectZoomApp(decodedCallbackState, code, userId, teamId);
case OFFICE_365_VIDEO:
return await this.office365VideoService.connectOffice365App(
decodedCallbackState,
code,
userId,
teamId
);
default:
throw new BadRequestException(
"Invalid conferencing app, available apps are: ",
[ZOOM, OFFICE_365_VIDEO].join(", ")
);View on GitHub (pinned to 176037d0af)
Solutions
- Restart the OAuth flow with a fresh access token if the user's session expired mid-connect.
- Verify the access token is still valid via a me/whoami call before generating the auth URL.
- Lengthen token TTL or implement silent refresh so the OAuth round-trip survives.
- Ensure the state is generated server-side from the current valid token, not a stale client-cached value.
Example fix
// before: client caches token
const state = { accessToken: cachedToken, ... };
// after: validate token freshness before starting the flow
const me = await api.me();
if (!me) { await reauthenticate(); return; }
const { data } = await api.getOauthUrl(app); // server uses fresh token in state Defensive patterns
Strategy: validation
Validate before calling
async function accessTokenIsValid(tokensRepository: TokensRepository, token: string): Promise<boolean> {
const ownerId = await tokensRepository.getAccessTokenOwnerId(token);
return ownerId !== null;
}
const state: OAuthCallbackState = JSON.parse(req.query.state);
if (!(await accessTokenIsValid(tokensRepository, state.accessToken))) {
throw new UnauthorizedException('Session expired — please restart the OAuth flow.');
} Type guard
function hasFreshAccessToken(state: OAuthCallbackState & { accessTokenIssuedAt?: number }): boolean {
const maxAgeMs = 1000 * 60 * 60 * 24 * 30; // 30 days
return Boolean(state.accessToken) && (state.accessTokenIssuedAt ?? Date.now()) > Date.now() - maxAgeMs;
} Try / catch
try {
await conferencingService.connectOauthApps(app, code, decodedCallbackState, teamId);
} catch (e) {
if (e instanceof UnauthorizedException && /Invalid Access token/.test(e.message)) {
// restart the flow with a fresh token
return res.redirect('/auth/login?next=/conferencing/connect');
}
throw e;
} Prevention
- Generate the OAuth state from the current valid token at request time, not a cached one.
- Validate the access token via a me() call before generating the auth URL.
- Implement silent token refresh so the OAuth round-trip survives longer than the token TTL.
- Track token issuance time and warn users before expiry.
When it happens
Trigger: OAuth callback fires with a state.accessToken whose owner token was deleted (user logged out, token rotated); token expired during the OAuth round-trip; state was tampered with; user started the OAuth flow in one session and completed it in another (different accessToken).
Common situations: Long OAuth round-trip where the access token expired (default 30-day expiry); user cleared cookies mid-flow; access token revoked by an admin; dev/test token from a destroyed environment.
Related errors
- PermissionsGuard - no oAuth client found for access token=${
- ApiAuthStrategy - access token - Invalid Access Token.
- ApiAuthStrategy - access token - Invalid Access Token.. No o
- No valid credentials available for Google Calendar
- Google Calendar credentials are invalid. Please reconnect.
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/fc6e1ef1a553461e.
Report an issue: GitHub.