calcom/cal.diy · error · UnauthorizedException

Invalid Access token.

Error message

Invalid Access token.

What it means

Thrown by GoogleCalendarService.saveCalendarCredentialsAndRedirect after the OAuth redirect callback returns from Google. The handler calls tokensService.getAccessTokenOwnerId(accessToken) to resolve the Cal API user that owns the supplied platform access token; if no owner row maps to the token, the request is treated as unauthenticated and HTTP 401 UnauthorizedException('Invalid Access token.') is raised. This protects the OAuth-code exchange step (the subsequent oAuth2Client.getToken call and credential persistence) so a credential is only ever stored against a verified user.

Source

Thrown at apps/api/v2/src/platform/calendars/services/gcal.service.ts:151

    isDryRun?: boolean
  ) {
    // User chose not to authorize your app or didn't authorize your app
    // redirect directly without oauth code
    if (!code || code === "undefined") {
      return { url: redir || origin };
    }

    // if isDryRun is true we know its a dry run so we just redirect straight away
    if (isDryRun) {
      return { url: redir || origin };
    }

    const parsedCode = z.string().parse(code);

    const ownerId = await this.tokensService.getAccessTokenOwnerId(accessToken);

    if (!ownerId) {
      throw new UnauthorizedException("Invalid Access token.");
    }

    const oAuth2Client = await this.getOAuthClient(this.redirectUri);
    const token = await oAuth2Client.getToken(parsedCode);
    // Google oAuth Credentials are stored in token.tokens
    const key = token.tokens;

    oAuth2Client.setCredentials(key);

    const calendar = new calendar_v3.Calendar({
      auth: oAuth2Client,
    });

    const cals = await calendar.calendarList.list({ fields: "items(id,summary,primary,accessRole)" });

    const primaryCal = cals.data.items?.find((cal) => cal.primary);

    if (primaryCal?.id) {

View on GitHub (pinned to 176037d0af)

Solutions

  1. Re-authenticate via the platform OAuth flow to mint a fresh access token, then retry the calendar callback.
  2. Verify the token being passed is the Cal.com platform access token (not a Google OAuth code or Cal refresh token) and that it is sent in the header/param the handler expects.
  3. Confirm the API host issuing the token and the host serving the callback are the same deployment and share the same token-signing secret/database.
  4. Inspect the tokensService.getAccessTokenOwnerId implementation to confirm the token is being decoded against the correct secret and that the owner row still exists.

Example fix

// before
const res = await fetch(`/v2/calendars/google/connect/callback?code=${code}`, { headers: {} });

// after
const res = await fetch(`/v2/calendars/google/connect/callback?code=${code}`, {
  headers: { Authorization: `Bearer ${calAccessToken}` },
});
Defensive patterns

Strategy: validation

Validate before calling

async function ensureTokenOwner(tokensService, accessToken) {
  const ownerId = await tokensService.getAccessTokenOwnerId(accessToken);
  if (!ownerId) {
    return { ok: false, reason: 'Invalid Access token.' };
  }
  return { ok: true, ownerId };
}

Type guard

function isNonEmptyToken(t: unknown): t is string {
  return typeof t === 'string' && t.trim().length > 0 && t !== 'undefined';
}

Try / catch

try {
  await googleCalendarService.saveCalendarCredentialsAndRedirect(code, accessToken, origin);
} catch (e) {
  if (e instanceof UnauthorizedException && e.message === 'Invalid Access token.') {
    // re-authenticate the user, then retry once
  } else throw e;
}

Prevention

When it happens

Trigger: Calling GET /v2/calendars/google/connect/callback (or saveCalendarCredentialsAndRedirect) with an accessToken that is expired, revoked, malformed, or belongs to no user; passing the same token after it was rotated; the Authorization header / query token is missing so accessToken resolves to undefined.

Common situations: Token TTL elapsed between the start of the OAuth flow and the redirect callback; the access token was issued by a different Cal API deployment/environment (env mismatch); the client sent the refresh token or an opaque ID instead of the platform access token; clock skew on the server makes a valid token appear expired.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/b7ef55d69e41b1a3. Report an issue: GitHub.