Geo: Derive OIDC redirect_uri from proxying secondary site
What does this MR do and why?
Geo: Derive OIDC redirect_uri from proxying secondary site
What does this MR do and why?
With Geo separate URLs + secondary proxying, OIDC sign-in can only ever complete on one site's domain. omniauth_openid_connect sends the static client_options.redirect_uri for every login regardless of which site the user signed in on (gem source), so the callback lands on the wrong domain, where the OmniAuth state isn't in the session, failing with Invalid 'state' parameter. This has been a documented limitation since 2023 and affects six customers on the issue.
The EE mechanism for per-site hosts already exists: on a proxied request, OmniAuth's full_host resolves to the proxying secondary's URL (ee/lib/ee/gitlab/omniauth_initializer.rb, via Gitlab::Geo.proxied_site(env)) — the OIDC gem just never consults it. SAML already derives its callback per-request via full_host, which is why our docs' "don't configure an ACS URL" guidance works per-site for SAML (see #491634 (closed)).
The fix: a module prepended to OmniAuth::Strategies::OpenIDConnect that derives redirect_uri as full_host + callback_path when the request carries a valid Geo proxy header, aligning OIDC with SAML's existing behaviour. Requests without the header (all non-Geo instances, direct requests to the primary) hit super — unchanged. The guard requires the JWT-signed Workhorse header (ee/lib/gitlab/geo/signed_data.rb), which can't be spoofed without a secondary's signing key. Both OIDC legs (authorize + token exchange) see the same proxied request env, so both derive the same value, satisfying OIDC's redirect_uri consistency requirement.
Gated behind new ops flag geo_oidc_proxied_redirect_uri, disabled by default.
Admin prerequisite (documented): register every site's callback URL (https://<site-external-url>/users/auth/openid_connect/callback) as a valid redirect URI on the OIDC client at the IdP — standard multi-domain OIDC, one client, multiple redirect URIs.
Closes #396745.
Scope / known limitations
openid_connectstrategy only; OAuth2-family providers with static redirect config (e.g.azure_oauth2) are follow-up material.- The post-login landing redirect still targets the primary's host on CNG deployments with default ingress config — that's #381573 (NGINX Ingress clobbers
X-Forwarded-Host; Rails support exists since !84368 (merged)), resolved by the workaround documented there (use-forwarded-headers: true). Verified locally that this MR + that workaround = fully seamless flow. - OIDC logout (
post_logout_redirect_uri) has the same static shape — follow-up. - Prepending to OpenIDConnect also covers subclasses such as CellsAwareOpenidConnect. The override is inert unless Geo is enabled, the ops flag is enabled, and the request carries a valid signed Geo proxy header - none of which hold on current Cells deployments (Geo does not run on GitLab.com today).
Testing summary
Validated on a live two-site Geo pair (Helm chart 10.1.1 / GitLab 19.1.1 CNG, separate URLs + proxying, Keycloak 26):
| Test | Setup | Result |
|---|---|---|
| Repro | Customer config, stock images | Secondary login fails Invalid 'state' parameter; callback lands on primary domain |
| Config matrix | Static redirect_uri pointed at each site in turn | Exactly one site works per static value — no configuration fixes this |
| Fix | These exact files mounted into stock images, flag enabled | Both sites log in concurrently on their own domains; both OIDC legs consistent; clone widget shows per-site URLs |
| Flag off | Same, flag at default (disabled) | Stock behaviour, bit-for-bit |
| First login | Brand-new IdP user via secondary | User auto-created; callback executes on primary Rails (0 processed by secondary) — no replica-write issues |
| Regression | — | Geo replication/verification health green; accelerated git clone via secondary; password login; non-proxied OIDC unchanged |
How to set up and validate locally
Needs a two-site Geo environment with separate URLs and secondary proxying — GDK Geo (two GDKs) works; no cloud-native setup required. Plus any OIDC IdP — a local Keycloak container is easiest:
- Run Keycloak, create a realm, a confidential client, and a test user. Register both sites' callback URLs on the client:
http://<primary>/users/auth/openid_connect/callbackandhttp://<secondary>/users/auth/openid_connect/callback. - Configure the same
openid_connectOmniAuth provider on both sites (client_options.redirect_uripointing at the primary, as a real customer would have it). - Without the flag: sign in via the secondary's URL → observe the redirect to the IdP carrying the primary's
redirect_uri, and the failureCould not authenticate you from OpenIDConnect because "Invalid 'state' parameter". Feature.enable(:geo_oidc_proxied_redirect_uri)on the primary.- Sign in via the secondary again → the IdP authorize URL now carries the secondary's
redirect_uri(visible in the browser URL bar) → login completes with the session on the secondary's domain; the project Code dropdown shows the secondary's clone URLs. - Sign in via the primary in parallel → unaffected.
Changelog: fixed
EE: true
References
Screenshots or screen recordings
| Before | After |
|---|---|
How to set up and validate locally
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #396745