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
- Ensure session cookies use `SameSite=Lax` (or `SameSite=None; Secure`) so they survive the OAuth redirect chain.
- On a 401 here, redirect to login and restart the OAuth flow rather than showing a bare error.
- 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
- Use SameSite=Lax (or None; Secure) for session cookies so they survive the Google redirect chain.
- Ensure the callback completes in the same browser session/cookie jar that started /add.
- On 401 at the callback, redirect to login and restart OAuth rather than dead-ending.
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
- You must be logged in to do this
- Session user must have an email
- `code` must be a string
- You must grant all permissions to use this integration
- Invalid Access token.
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/cd5b709691ea98b4.
Report an issue: GitHub.