Nginx OAuth2-Proxy Integration#
OAuth2-Proxy integrates with nginx via the ngx_http_auth_request_module. Nginx delegates every incoming request to oauth2-proxy's /oauth2/auth endpoint; based on the response, nginx either allows the request through or triggers an error_page handler. Requires --reverse-proxy flag on oauth2-proxy.
The /oauth2/auth Endpoint (Policy Oracle)#
The AuthOnly handler in oauthproxy.go is the backend for auth_request. It returns:
| Status | Condition |
|---|---|
| 202 Accepted | Valid session + passes authorization constraints |
| 401 Unauthorized | No valid session (unauthenticated) |
| 403 Forbidden | Valid session but not authorized (wrong group/email) |
The endpoint never redirects. nginx's auth_request module treats any non-2xx/401/403 response as an internal error (500). This was the root cause behind PR #3309 being reverted — changing /auth to emit 302 redirects broke all nginx deployments.
Browser Flow: Named-Location Redirect Pattern#
For browser-facing routes, the correct approach is to let nginx itself issue the redirect after receiving a 401 from /oauth2/auth. Use a named location rather than error_page 401 =403 /oauth2/sign_in (the older pattern):
location / {
auth_request /oauth2/auth;
error_page 401 = @oauth2_signin; # delegates to named location
# ...proxy_pass, auth_request_set headers, etc.
}
location @oauth2_signin {
return 302 /oauth2/sign_in?rd=$scheme://$host$request_uri;
}
Why not error_page 401 =403? That pattern returns a 403 status with a Location header. Browsers do not automatically follow redirects on 403 responses — users see a "Found." page instead of being redirected, which breaks --skip-provider-button=true.
API Flow: Pass-Through Status Codes#
API and machine-to-machine routes should not redirect. Pass the 401 straight through:
location /api/ {
auth_request /oauth2/auth;
error_page 401 =401; # preserve the 401 for API clients
proxy_pass http://backend/;
}
This keeps API clients getting clean 401/403 responses without a redirect loop.
Wiring the /oauth2/ Locations#
Two locations must be proxied to oauth2-proxy for the flow to work:
/oauth2/— proxied to oauth2-proxy; forwardsX-Auth-Request-Redirectso oauth2-proxy knows where to return the user after login./oauth2/auth(exact match) — proxied withproxy_pass_request_body offandContent-Length "", sinceauth_requestonly forwards headers, not the request body.
Propagating User Identity to the Backend#
After a successful auth check, use auth_request_set to capture response headers from oauth2-proxy and forward them to the upstream:
$upstream_http_x_auth_request_user→X-User(requires--set-xauthrequest)$upstream_http_x_auth_request_email→X-Email$upstream_http_x_auth_request_access_token→X-Access-Token(requires--pass-access-token)
Cookie Refresh & Multi-Part Cookies#
When --cookie-refresh is set, the refreshed Set-Cookie header must be copied from the subrequest back to the browser response:
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
nginx normally only copies the first Set-Cookie header from auth_request. With --set-authorization-header, tokens can exceed the 4 KB cookie limit and oauth2-proxy splits them into multiple cookies. For multi-part cookies, extract and re-emit each part manually using $upstream_cookie_<name>_1 variables. Use --session-store-type=redis to avoid this complexity when dealing with large OIDC tokens (e.g., Azure).
Kubernetes / ingress-nginx#
The same pattern maps directly to ingress annotations:
nginx.ingress.kubernetes.io/auth-url: "https://<oauth2-proxy-fqdn>/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://<oauth2-proxy-fqdn>/oauth2/start?rd=$escaped_request_uri"
Full ingress-nginx example: https://kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth/
Key References#
- nginx.md integration guide — canonical config examples
AuthOnlyin oauthproxy.go —/oauth2/authimplementation- PR #3315 — named-location redirect pattern docs
- PR #3314 — why
/oauth2/authmust not return 302 redirects