Issues / #102

Move iamnim OAuth redirect_uri from cookie into the OAuth state parameter

open improvement Project: iamnim Reporter: 1 May 2026 18:47

Description

iamnim today stores the post-login redirect_uri in a short-lived cookie (`iamnim_redirect`, MaxAge=600s, SameSite=Lax) set in `internal/web/google.go:147`. On the Google callback (`google.go:223`) it reads the cookie, and if missing, silently falls through `redirectAfterLogin` (server.go:420) to `/dashboard` — leaving the user logged in but stranded on iamnim.com instead of being bounced back to the app they came from.

Observed (during issue #95 investigation): on 2026-04-30 08:59:34, iamnim logged `[Auth] Google callback: no redirect_uri cookie found` for emilie@executxr.com. She had a valid Pantheon identity and had previously logged in successfully — just the cookie didn't survive the round trip this time.

The cookie can be lost when:
- User takes >10 min between clicking 'Sign in with Google' and finishing Google's consent screen.
- User completes a stale consent screen left open from an earlier attempt.
- Browser strict tracking protection clears the cookie on cross-site bounce (Safari ITP, Brave, Firefox strict).

Fix structurally: encode redirect_uri into the OAuth `state` parameter — which is already round-tripped through Google and validated server-side — instead of a side cookie.

Concrete change:

1. `generateState()` in `internal/web/google.go` (line ~115): generate random state token, store `{redirect_uri, created_at}` in `g.states[state]` map instead of just `true`. Garbage-collect entries older than ~30 min.
2. `handleGoogleAuth` (line 140): pass the redirect_uri into `generateState`; remove the cookie set.
3. `handleGoogleCallback` (line 161): read the redirect_uri from the state map after `Exchange` validates state; remove the cookie lookup.

Apply the same to the GitHub, Google Ads, Meta Ads, and Google Drive OAuth flows for consistency.

UX improvement to ship alongside: when redirect_uri is genuinely absent or disallowed, render a friendly 'You're signed in. Return to <app> to continue' page on iamnim instead of dumping the user on `/dashboard` with no signal.