Envoy and Istio Integration#
oauth2-proxy can act as an Envoy external authorization (ext_authz) service, delegating authentication decisions to oauth2-proxy via the /oauth2/auth endpoint. In Istio, this is wired through meshConfig.extensionProviders and an AuthorizationPolicy with action: CUSTOM. There is no dedicated Envoy/Istio integration guide in the official oauth2-proxy docs β the documented patterns focus on nginx auth_request and Traefik forwardAuth, but the ext_authz model is conceptually equivalent.
Header Propagation Options#
Three legacy flags control how oauth2-proxy propagates identity information in its auth response. All are defined in LegacyHeaders and convert to the InjectResponseHeaders / InjectRequestHeaders system at runtime .
| Flag | Config key | What it does |
|---|---|---|
--set-xauthrequest | set_xauthrequest | Adds X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Preferred-Username, X-Auth-Request-Groups to the auth response |
--set-authorization-header | set_authorization_header | Sets Authorization: Bearer <id_token> in the auth response |
--pass-access-token | pass_access_token | Passes the OAuth access_token upstream via X-Forwarded-Access-Token; also adds X-Auth-Request-Access-Token when combined with --set-xauthrequest |
--pass-authorization-header (pass_authorization_header) is a separate request-side flag that forwards the OIDC ID token to the upstream service as Authorization: Bearer, rather than in the auth response .
For advanced scenarios, all headers are fully configurable via the alpha YAML API under injectRequestHeaders and injectResponseHeaders, bypassing the legacy flags entirely .
Known Issue: Istio Filtering the Authorization Header#
Gateway-level header stripping#
Istio gateways can filter the Authorization header before it ever reaches oauth2-proxy. This prevents oauth2-proxy from reading the bearer token from incoming requests β the request arrives at oauth2-proxy with no Authorization header even though the client sent one. This is tracked in issue #2520, where a maintainer noted an in-progress workaround via PR #3262.
PR #3262 (feat: allow setting AuthorizationHeaderName, currently open) adds an AuthorizationHeaderName option so deployments can use a non-standard header name (e.g., Authorization-Custom) to carry tokens, bypassing the gateway's built-in filtering .
RequestAuthentication conflict with token refresh#
A more subtle problem arises in deployments that combine oauth2-proxy ext_authz with Istio RequestAuthentication :
- oauth2-proxy session refresh is request-driven β it only triggers when a request arrives with a valid session cookie.
- When oauth2-proxy successfully refreshes a session, it returns the new ID token via
Authorization: Bearer <new_token>in the ext_authz response (requiresset_authorization_header = true) . - However, Istio's
RequestAuthenticationvalidates the JWT on the incoming request, which still carries the original (now-expired) ID token. If JWT validation runs before or independently of ext_authz, the request is rejected with 401 before oauth2-proxy can refresh it. - Even when oauth2-proxy does return a refreshed token, if the Envoy ext_authz filter only allows a subset of response headers (e.g., only
kubeflow-userid), theAuthorizationheader is dropped and neither the upstream service norRequestAuthenticationsees the updated token.
Workarounds:
- Verify Istio filter chain ordering so ext_authz runs before
RequestAuthenticationJWT validation. - Add
Authorizationto the ext_authz allowed response headers so Envoy propagates the refreshed token downstream. - Consider removing
RequestAuthenticationfor paths where oauth2-proxy is already the authentication authority β running both independently creates conflicts during token refresh.
Envoy original-path redirect loop#
When oauth2-proxy is deployed behind an Istio VirtualService with URI prefix rewriting, the post-authentication redirect can loop because oauth2-proxy only sees the rewritten (short) path, not the original one. Envoy injects the original path in X-Envoy-Original-Path. PR #2947 (open) adds --app-redirect-header / app_redirect_header to configure which request header oauth2-proxy should read for the redirect target .
Additional Notes#
-
Real client IP:
X-Envoy-External-Addressis supported as a value forreal_client_ip_header, allowing oauth2-proxy to correctly resolve the client IP when running behind Envoy (merged in PR #2755) . -
Group-based authorization via headers: A closed PR #1727 explored
--allowed-groups-headerto let a single oauth2-proxy instance enforce different group policies per Istio route. -
Header stripping: By default, oauth2-proxy strips incoming
X-Forwarded-*andAuthorizationheaders that it would set itself, preventing spoofing. This is controlled byskip-auth-strip-headers(default:true). In Istio deployments, verify this does not conflict with headers Envoy injects legitimately. -
Session store for Istio/Kubeflow: When multiple concurrent browser requests race to refresh the same session (common with Kubeflow notebooks behind Istio), Redis session storage is strongly recommended. Redis enables distributed locking that serializes refresh attempts and prevents duplicate refresh token use with rotation-enabled providers .
Key Source Files#
| File | Purpose |
|---|---|
pkg/apis/options/legacy_options.go | LegacyHeaders struct; --set-xauthrequest, --set-authorization-header, --pass-access-token flag definitions and conversion to InjectResponseHeaders |
pkg/apis/options/alpha_options.go | Alpha API for fully custom injectRequestHeaders / injectResponseHeaders |
pkg/middleware/stored_session.go | Session refresh logic and Redis distributed lock |