calcom/cal.diy · warning · HttpError

You must be logged in to do this

Error message

You must be logged in to do this

What it means

Auth guard in the OAuth callback, run after the code check. Fires `HttpError` **401** when `req.session?.user?.id` is absent. Because this runs inside the redirect roundtrip to Google and back, the dominant cause is the session cookie not surviving that redirect chain (SameSite/third-party cookie blocking, or session expiry mid-flow).

Source

Thrown at packages/app-store/googlecalendar/api/callback.ts:43

async function getHandler(req: NextApiRequest, res: NextApiResponse) {
  const { code } = req.query;
  const state = decodeOAuthState(req);

  if (typeof code !== "string") {
    if (state?.onErrorReturnTo || state?.returnTo) {
      res.redirect(
        getSafeRedirectUrl(state.onErrorReturnTo) ??
          getSafeRedirectUrl(state?.returnTo) ??
          `${WEBAPP_URL}/apps/installed`
      );
      return;
    }
    throw new HttpError({ statusCode: 400, message: "`code` must be a string" });
  }

  if (!req.session?.user?.id) {
    throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
  }

  const { client_id, client_secret } = await getGoogleAppKeys();

  const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`;

  const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);

  if (code) {
    const token = await oAuth2Client.getToken(code);
    const key = token.tokens;
    const grantedScopes = token.tokens.scope?.split(" ") ?? [];
    // Check if we have granted all required permissions
    const hasMissingRequiredScopes = GOOGLE_CALENDAR_SCOPES.some((scope) => !grantedScopes.includes(scope));
    if (hasMissingRequiredScopes) {
      if (!state?.fromApp) {
        throw new HttpError({
          statusCode: 400,

View on GitHub (pinned to 176037d0af)

Solutions

  1. Ensure session cookies use `SameSite=Lax` (or `SameSite=None; Secure`) so they survive the OAuth redirect chain.
  2. On a 401 here, redirect to login and restart the OAuth flow rather than showing a bare error.
  3. Confirm the callback is reached in the same browser session that hit `/add` (same cookie jar).
Defensive patterns

Strategy: validation

Try / catch

// On a 401 from the callback, bounce to login and restart the OAuth flow
// (callback is a top-level redirect target, so guard at the /add entry instead)
async function startGoogleOAuth() {
  const res = await fetch("/api/integrations/googlecalendar/add", { redirect: "manual" });
  if (res.status === 401) {
    window.location.href = `/auth/login?callbackUrl=${encodeURIComponent("/apps/installed")}`;
    return;
  }
  const { url } = await res.json();
  window.location.href = url; // same browser session must complete the Google roundtrip
}

Prevention

When it happens

Trigger: User starts Google install, is redirected to Google and back, but by the callback the session is gone: cookie blocked across the redirect, session TTL elapsed during a slow consent, or the callback completed in a different browser context than the one that started the flow.

Common situations: Safari/Chrome ITP blocking third-party cookies across the Google redirect; session cookie expiring during consent; user finishing consent on a different device/incognito tab; cookie domain mismatch between `WEBAPP_URL` and the callback host.

Related errors


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