OAuth2-Proxy Header Injection#
Header injection is how oauth2-proxy propagates authenticated session claims β user identity, email, groups, tokens, and custom OIDC claims β into HTTP request and response headers before forwarding them to upstream services. It is central to patterns like NGINX auth_request, where the upstream reads X-Auth-Request-User, X-Auth-Request-Email, and X-Auth-Request-Groups to authorize the caller.
Architecture#
Injection is a two-layer pipeline:
options.Header config
β
βΌ
header.NewInjector() β pkg/header/injector.go
β
ββ newSecretInjector() (static value from env/file/literal)
ββ newClaimInjector() (dynamic value from SessionState)
β
βΌ
SessionState.GetClaim() β pkg/apis/sessions/session_state.go
β
βΌ
middleware.NewRequestHeaderInjector() / NewResponseHeaderInjector()
β β
req.Header rw.Header()
pkg/header/injector.go β The core abstraction. NewInjector accepts a []options.Header config slice and builds a flat list of valueInjector closures β one per (header name, value source) pair. At request time, Inject(header, session) iterates every closure and calls header.Add().
Each HeaderValue holds exactly one source β either a SecretSource (static literal, env var, or file) or a ClaimSource (session claim). Supplying both is an error .
pkg/apis/options/header.go β Defines the config structs:
Header: holds the headername, apreserveRequestValuetoggle, anInsecureSkipHeaderNormalizationtoggle, and a[]HeaderValueslice.ClaimSource: specifies theclaimname (see available claims), an optionalprefix, and an optionalbasicAuthPasswordforAuthorization: Basicencoding.
pkg/middleware/headers.go β Wraps the injector as Alice middleware. NewRequestHeaderInjector additionally prepends a stripping pass that removes any incoming header matching the configured name (normalized) before injecting β preventing clients from spoofing injected headers. Response injection via NewResponseHeaderInjector writes to rw.Header() instead. After injection, flattenHeaders collapses multi-value headers into a single comma-joined string for all headers except Set-Cookie.
Available Claims#
SessionState.GetClaim maps claim names to SessionState fields and returns []string:
| Claim name | Source field | Notes |
|---|---|---|
user | SessionState.User | |
email | SessionState.Email | |
groups | SessionState.Groups | Multi-valued; yields one header.Add() call per group |
preferred_username | SessionState.PreferredUsername | |
access_token | SessionState.AccessToken | |
id_token | SessionState.IDToken | |
refresh_token | SessionState.RefreshToken | |
created_at | SessionState.CreatedAt | |
expires_on | SessionState.ExpiresOn | |
| (any other string) | SessionState.AdditionalClaims | Custom OIDC claims |
Groups are inherently multi-valued. GetClaim("groups") returns the full []string slice, and newClaimInjector calls header.Add(name, claim) for each element . This means X-Auth-Request-Groups will appear as multiple header instances β unless flattenHeaders collapses them to a comma-separated value .
Default Headers: --set-xauthrequest#
The legacy flag --set-xauthrequest enables a fixed set of response headers. Internally, it calls getXAuthRequestHeaders(), which constructs four options.Header entries:
| Header | Claim |
|---|---|
X-Auth-Request-User | user |
X-Auth-Request-Email | email |
X-Auth-Request-Preferred-Username | preferred_username |
X-Auth-Request-Groups | groups |
All four are added to InjectResponseHeaders . The flag description in code still says "X-Auth-Request-User and X-Auth-Request-Email" but the actual implementation also adds groups and preferred username β a documentation lag from when groups support was added.
Custom Header Configuration (Alpha API)#
Beyond the legacy flag, headers are fully configurable via the alpha YAML API under injectRequestHeaders and injectResponseHeaders . Example patterns:
- Forwarding the ID token as a Bearer token:
ClaimSource{Claim: "id_token", Prefix: "Bearer "}(see injector_test.go) - Basic auth from user claim:
ClaimSource{Claim: "user", BasicAuthPassword: &SecretSource{...}}β encodesuser:passwordasAuthorization: Basic <base64> - Static headers from env/file:
SecretSource{FromEnv: "MY_VAR"} - Multi-value same header: list multiple
Valuesentries under one header name; each becomes an additionalheader.Add()call
Header Normalization and Stripping#
By default, header names are Title-Cased and underscores converted to dashes, following common HTTP practice . Set InsecureSkipHeaderNormalization: true per header to disable this. Normalization also governs what incoming headers get stripped: stripNormalizedHeader compares normalized forms to remove case/underscore variants of the configured name before injecting. To preserve an incoming header value instead of stripping it, set preserveRequestValue: true .
Key Files#
| File | Purpose |
|---|---|
pkg/header/injector.go | Core Injector interface, NewInjector factory, claim/secret injector closures |
pkg/header/injector_test.go | Behavioral tests covering all injector modes |
pkg/apis/options/header.go | Header, HeaderValue, ClaimSource struct definitions |
pkg/apis/sessions/session_state.go | GetClaim β claim name β session field mapping |
pkg/middleware/headers.go | Alice middleware wrappers for request/response injection + strip/flatten logic |
pkg/apis/options/legacy_options.go | getXAuthRequestHeaders β default --set-xauthrequest header definitions |