Skip to content

Commit 59cd74b

Browse files
authored
Merge pull request #130 from EqualifyEverything/fix/oidc-behind-reverse-proxy
fix(deploy): enable uvicorn --proxy-headers for OIDC behind reverse proxies
2 parents cfc6283 + 3ed4124 commit 59cd74b

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

Dockerfile

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,16 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
9595
# Expose API port
9696
EXPOSE 8080
9797

98-
# Production command (no reload for stability)
99-
CMD ["uv", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080", "--workers", "1"]
98+
# Production command (no reload for stability).
99+
#
100+
# --proxy-headers + --forwarded-allow-ips=* tells uvicorn to honour the
101+
# X-Forwarded-Proto / X-Forwarded-For headers set by an upstream reverse
102+
# proxy (ALB, Nginx, Cloudflare). Without this, request.url.scheme inside
103+
# the container reads "http" even when the user hit "https", so any
104+
# scheme-aware code path silently builds wrong URLs — most visibly the
105+
# OIDC redirect_uri, which then mismatches what's registered with the IdP
106+
# and login fails with AADSTS50011 (or the equivalent on Google/Okta/...).
107+
# The "*" allowlist is safe behind ECS+ALB because the security group
108+
# restricts ingress on :8080 to the ALB only, and the ALB strips any
109+
# client-supplied X-Forwarded-* before forwarding.
110+
CMD ["uv", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080", "--workers", "1", "--proxy-headers", "--forwarded-allow-ips=*"]

docs/how-to/configure-sso.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,20 @@ AUTH_OIDC_PROVIDERS=[
126126
]
127127
```
128128

129+
## Behind a reverse proxy (ALB, Nginx, Cloudflare)
130+
131+
OIDC redirect URIs are **scheme-sensitive** — Entra (and all other IdPs) reject the round-trip if the `redirect_uri` we send doesn't match what's registered byte-for-byte. When the app sits behind a reverse proxy that terminates HTTPS, the container itself sees only HTTP, and naive `request.url.scheme` reads return `"http"` — building an `http://...` redirect URI for a service the user actually reached over `https://...`. Login fails with `AADSTS50011` on Entra, equivalent codes elsewhere.
132+
133+
The shipped Dockerfile already addresses this: the production CMD includes `--proxy-headers --forwarded-allow-ips=*`, which tells uvicorn to read `X-Forwarded-Proto` and reflect it back into `request.url.scheme`.
134+
135+
What you must verify in your deployment:
136+
137+
- **Your reverse proxy sets `X-Forwarded-Proto`.** AWS ALB, GCP Cloud Load Balancer, Azure App Gateway, and Cloudflare all do this by default. Nginx needs an explicit `proxy_set_header X-Forwarded-Proto $scheme;`.
138+
- **The container is reachable only via the proxy.** Otherwise a direct caller could spoof `X-Forwarded-Proto: https` and trick the app into building wrong URIs. ECS security groups, Nginx upstream configs, and equivalent network controls handle this.
139+
- **You're not running uvicorn with custom flags that drop `--proxy-headers`.** If you've replaced the Dockerfile CMD, keep the flag.
140+
141+
If a working local OIDC round-trip suddenly fails the moment you put the app behind a proxy, this is almost certainly the cause. Confirm by checking the kickoff redirect URL — `redirect_uri=https%3A%2F%2F…` is correct; `redirect_uri=http%3A%2F%2F…` against an HTTPS-fronted host means proxy headers aren't being honoured.
142+
129143
## Common pitfalls
130144

131145
- **AADSTS50011: redirect URI does not match.** The URI registered with Entra must match the request's host + scheme + path *exactly*. If your deployment is behind an ALB that terminates HTTPS, register `https://...` even though the FastAPI process internally sees HTTP. Confirm the ALB sets `X-Forwarded-Proto: https` and that Starlette is reading it (otherwise our `_redirect_uri_for` builds the wrong URL).

0 commit comments

Comments
 (0)