Description
Customer-reported bug: clicking Log out on app.composio.dev appears to log out for an instant, then bounces the user right back to the same page they were on. Repros across all surfaces (StandaloneLogout, MenuUser dropdown, etc.).
Root cause — /auth/clear was calling cookieStore.delete(cookie.name) (no Domain). Apollo's Better Auth scopes the session cookie to Domain=.composio.dev in deployed envs (hermes/apps/apollo/src/lib/better_auth/index.ts:558). A Set-Cookie deletion without a matching Domain only clears the host-only variant — the parent-domain cookie persists in the browser. After /auth/clear redirects to /login, the browser still sends the cookie; if the best-effort server-side signOut happens to fail (the catch swallows the error), Apollo accepts the cookie, /login sees a valid session and redirects to /, then / follows last_visited back to the page the user came from — exactly the customer's report.
Bonus complications the fix has to handle:
- The cookie shape differs by deployment. When the dashboard is off
.composio.dev (localhost dev, *.vercel.app ad-hoc previews), the Better Auth proxy in ~/clients/auth/better-auth.ts:18-21 strips Domain=.composio.dev and stores the cookie host-only. Hard-coding Domain=.composio.dev would break those.
/api/dev-login (gated to non-prod target envs) creates host-only session cookies even on staging/preview deployments served from .composio.dev. Both shapes can coexist on the same browser.
- Next's
cookies().delete(name) cannot emit two Set-Cookie headers for the same cookie name — ResponseCookies keeps pending entries in a Map keyed by name (compiled @edge-runtime/cookies ResponseCookies#set + replace()), so a second delete() for the same name overwrites the first instead of appending.
Fix — switch /auth/clear to a manual NextResponse and append raw Set-Cookie headers:
- Always issue the host-only delete (matches dev-login + off-
.composio.dev Better Auth cookies).
- Additionally issue a
Domain=.composio.dev delete when both the dashboard URL and backend URL are under .composio.dev (matches Apollo's Better Auth cookies in deployed envs). Same shareCookieDomain derivation as better-auth.ts.
How did I test this PR
-
doppler run -- pnpm typecheck — ✅
-
doppler run -- pnpm lint — ✅ (the 2 pre-existing repo-level errors in support/page.tsx are unrelated)
-
Standalone simulation of the route's Set-Cookie output across all five deployment shapes (production, staging, preview-on-.preview.composio.dev, preview-on-*.vercel.app, local dev). Every cookie shape that can plausibly exist on each deployment now has a matching deletion header:
=== production / staging / preview on .composio.dev (sharesParentCookieDomain=true) ===
Set-Cookie: composio-dash.session_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: composio-dash.session_token=; Path=/; Domain=.composio.dev; Expires=Thu, 01 Jan 1970 00:00:00 GMT
=== preview on *.vercel.app (sharesParentCookieDomain=false) ===
Set-Cookie: composio-dash-staging.session_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
=== local dev (sharesParentCookieDomain=false) ===
Set-Cookie: composio-dash-local.session_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
-
Codex review — 4 iterations (initial + 3 fix passes). Caught two real bugs in earlier drafts: (1) Next's cookies().delete() map-by-name behavior collapsing two same-named deletes; (2) staging deployments on .composio.dev host both Better Auth (parent-domain) and dev-login (host-only) cookies simultaneously. Final iteration: "The change preserves the existing sign-out flow while emitting explicit host-only and parent-domain cookie deletions, which addresses the domain-matching issue without introducing an evident functional regression."
-
Live local browser test not feasible: dashboard couldn't compile in the sandbox (pre-existing Module not found: '@composio/db' resolution issue unrelated to this change). The fix is one route handler with no behavioral coupling outside the deletion call site, fully covered by typecheck + the per-scenario simulation above. Final E2E will be the deployed preview / production rollout.
Triggered by: palash@composio.dev | Source: slack
Session: https://zen-api-production-4c98.up.railway.app/dashboard/#/chat/zen-e86c4a3f09f8