CSRF Protection via State Cookie due to Cross-Cell OAuth Flow
In the standard OmniAuth flow, CSRF protection works by storing the state parameter in the server-side session during the request phase and validating it against the state returned in the callback URL. In the Cells architecture this breaks down — the callback may arrive at a different cell with no access to the originating cell's session. The [IAM Auth Service session storage](https://gitlab.com/gitlab-org/gitlab/-/work_items/591999) solves cross-cell transport of session params, but it cannot serve as the CSRF mechanism on its own. Because `state` is used as the session key, an attacker could initiate their own legitimate OAuth flow, obtain a valid `state`, and trick a victim into completing a callback with that `state` — a session fixation attack. The session storage would return a valid result and the flow would proceed. To prevent this, the `state` value will also be stored in a short-lived, `HttpOnly; Secure; SameSite=Lax` cookie scoped to the shared parent domain (e.g. `.gitlab.com`). Since all cells share this parent domain, whichever cell receives the callback will receive the cookie. The callback cell validates the state in the URL against the cookie before retrieving anything from the IAM session API. This browser-binds the flow — an attacker cannot complete the callback in a victim's browser with a state the victim's browser never set. ## Requirements ### Request Phase - During the OmniAuth request phase, a short-lived `oauth_state` cookie is set containing the state value: ``` Set-Cookie: oauth_state=<state>; Domain=.gitlab.com; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=600 ``` - The `Max-Age` value should match the IAM Auth Service parked session TTL (10 minutes) ### Callback Phase - On callback, the `state` parameter from the URL is validated against the `oauth_state` cookie **before** any call to the IAM Auth Service session API - If the cookie is absent or does not match the state URL parameter, validation fails and the flow is aborted with an OmniAuth failure - The `oauth_state` cookie is cleared immediately after successful validation, regardless of whether the rest of the callback phase succeeds or fails ## Acceptance Criteria - [x] `oauth_state` cookie is set on `.gitlab.com` domain during OmniAuth request phase - [x] Cookie attributes: `HttpOnly`, `Secure`, `SameSite=Lax`, `Max-Age=600` - [ ] Callback phase validates `state` URL param against `oauth_state` cookie before calling IAM session API - [ ] Mismatch or missing cookie aborts the flow with OmniAuth failure - [ ] Cookie is cleared immediately after successful validation ## Dependencies - Cross-cell session management [#591999](https://gitlab.com/gitlab-org/gitlab/-/work_items/591999) - GitLab Rails: OmniAuth OIDC setup [#592373](https://gitlab.com/gitlab-org/gitlab/-/issues/592373) ## References - Supersedes task [#592202](https://gitlab.com/gitlab-org/gitlab/-/work_items/592202) - Parent epic: [&21221 Federated Authentication With IAM Auth service](https://gitlab.com/groups/gitlab-org/-/epics/21221)
issue