DocumentsOAuth2 Proxy
Nginx OAuth2-Proxy Integration
Nginx OAuth2-Proxy Integration
Type
Topic
Status
Published
Created
Jul 9, 2026
Updated
Jul 9, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

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:

StatusCondition
202 AcceptedValid session + passes authorization constraints
401 UnauthorizedNo valid session (unauthenticated)
403 ForbiddenValid 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; forwards X-Auth-Request-Redirect so oauth2-proxy knows where to return the user after login.
  • /oauth2/auth (exact match) — proxied with proxy_pass_request_body off and Content-Length "", since auth_request only 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_userX-User (requires --set-xauthrequest)
  • $upstream_http_x_auth_request_emailX-Email
  • $upstream_http_x_auth_request_access_tokenX-Access-Token (requires --pass-access-token)

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#