calcom/cal.diy · error · UnauthorizedException
Invalid Access token.
Error message
Invalid Access token.
What it means
Thrown in connectZoomApp after a successful Zoom token exchange (status 200, no error) if userId is falsy. Because connectZoomApp signature requires userId: number, this guard fires when the caller passes 0, NaN coerced from undefined, or an explicitly falsy value. It raises UnauthorizedException (HTTP 401), distinct from a Zoom-side error.
Source
Thrown at apps/api/v2/src/modules/conferencing/services/zoom-video.service.ts:96
const responseBody = await result.json();
errorMessage = responseBody.error;
} catch (e) {
errorMessage = await result.clone().text();
}
throw new BadRequestException(errorMessage);
}
const responseBody = await result.json();
if (responseBody.error) {
throw new BadRequestException(responseBody.error);
}
responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000);
delete responseBody.expires_in;
if (!userId) {
throw new UnauthorizedException("Invalid Access token.");
}
const existingCredentialZoomVideo = teamId
? await this.credentialsRepository.findAllCredentialsByTypeAndTeamId(ZOOM_TYPE, teamId)
: await this.credentialsRepository.findAllCredentialsByTypeAndUserId(ZOOM_TYPE, userId);
const credentialIdsToDelete = existingCredentialZoomVideo.map((item) => item.id);
if (credentialIdsToDelete.length > 0) {
teamId
? await this.appsRepository.deleteTeamAppCredentials(credentialIdsToDelete, teamId)
: await this.appsRepository.deleteAppCredentials(credentialIdsToDelete, userId);
}
teamId
? await this.appsRepository.createTeamAppCredential(
ZOOM_TYPE,
responseBody as unknown as Prisma.InputJsonObject,
teamId,View on GitHub (pinned to 176037d0af)
Solutions
- Ensure the route is behind the API auth guard and that userId is extracted from request.user.id before calling connectZoomApp.
- Validate userId is a positive integer at the controller boundary and return 401 there with a clearer message.
- Check the OAuth state object is reconstructed correctly so the callback knows which user initiated the flow.
Example fix
// before
await zoomService.connectZoomApp(state, code, req.user?.id ?? 0, teamId);
// after
const userId = req.user?.id;
if (!userId || !Number.isInteger(userId) || userId <= 0) {
throw new UnauthorizedException('Authenticated user required.');
}
await zoomService.connectZoomApp(state, code, userId, teamId); Defensive patterns
Strategy: validation
Validate before calling
const userId = req.user?.id;
if (!Number.isInteger(userId) || (userId as number) <= 0) {
throw new UnauthorizedException('Authenticated user required to connect Zoom.');
}
await zoomService.connectZoomApp(state, code, userId as number, teamId); Type guard
const isPositiveUserId = (u: unknown): u is number => typeof u === 'number' && Number.isInteger(u) && u > 0;
Prevention
- Resolve and validate userId at the controller boundary, not deep in the service.
- Ensure ApiAuthGuard populates request.user before the handler runs.
- Encode userId in the OAuth state so callbacks can recover it.
When it happens
Trigger: The controller/handler invoking connectZoomApp failed to resolve the authenticated user id and passed 0/undefined/NaN. The token exchange itself succeeded, but the service refuses to persist a credential with no owner.
Common situations: ApiAuthGuard did not attach request.user; the handler reads userId from a session that expired mid-flow; OAuth callback landed without the state param carrying userId; refactor changed the calling signature and a falsy default slipped in.
Related errors
- Missing `state` query param
- {error_description}
- Invalid conferencing app. Available apps: GOOGLE_MEET.
- Invalid Access token.
- Invalid conferencing app, available apps are:
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/1b20c0363dbef443.
Report an issue: GitHub.