DocumentsOAuth2 Proxy
oauth2-proxy Configuration
oauth2-proxy Configuration
Type
Topic
Status
Published
Created
Jul 7, 2026
Updated
Jul 7, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

oauth2-proxy Configuration#

oauth2-proxy is a reverse proxy that gates access to upstream services using OAuth2/OIDC authentication. This article covers three key configuration areas: OIDC provider setup (automatic discovery vs. manual endpoint configuration), Keycloak-specific wiring, and ForwardAuth integration with Traefik in Kubernetes.

Configuration delivery supports three methods in decreasing precedence order: CLI flags, environment variables (prefixed OAUTH2_PROXY_, with hyphens replaced by underscores), and a TOML config file.

OIDC Discovery (Automatic Endpoint Resolution)#

The default OIDC mode — set --oidc-issuer-url and leave --skip-oidc-discovery false — triggers automatic discovery. On startup, NewProvider appends /.well-known/openid-configuration to the issuer URL and parses the response into authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, and supported signing algorithms .

By default, the discovered issuer field must exactly match the configured --oidc-issuer-url; mismatches are rejected . Use --insecure-oidc-skip-issuer-verification when the provider returns a different issuer (e.g., some Azure multi-tenant setups).

Signing algorithm resolution: if --oidc-enabled-signing-algs is set, the effective set is the intersection of the configured list and the provider's discovered id_token_signing_alg_values_supported. If there is no intersection, startup fails .

The ProviderVerifier interface exposes DiscoveryEnabled() to distinguish between auto-discovered and manually-configured providers — callers must check this before using endpoint data from the provider .

Manual Endpoint Configuration (Skip Discovery)#

Use --skip-oidc-discovery when the provider does not publish a discovery document, or when the issuer URL doesn't match the document. When this flag is set, you must supply either --oidc-jwks-url or --oidc-public-key-files — not both — along with --oidc-issuer-url (still required for token issuer validation) .

FlagTOML keyPurpose
--login-urllogin_urlAuthorization endpoint
--redeem-urlredeem_urlToken redemption endpoint
--oidc-jwks-urloidc_jwks_urlJWKS URI for token verification
--profile-urlprofile_urlUserInfo endpoint
--validate-urlvalidate_urlAccess token validation endpoint

Internally, legacy flags are converted to the OIDCOptions struct: SkipDiscovery , JwksURL , and IssuerURL . When discovery is skipped, the proxy creates a remote JWKS key set from the provided URL rather than fetching it from the discovery document.

If you have a local PEM public key instead of a JWKS URL, pass --oidc-public-key-files pointing to PEM-encoded files — newKeySetFromStatic loads them into a static key set.

Keycloak OIDC Provider#

Use --provider=keycloak-oidc (not the legacy keycloak provider). The issuer URL format changed in Keycloak 17+:

  • v17+: https://<keycloak-host>/realms/<realm>
  • pre-v17: https://<keycloak-host>/auth/realms/<realm>

Audience mapper (required): The Keycloak OIDC client must have an audience mapper that adds the client's name to the aud claim of the JWT. oauth2-proxy validates the token against --client-id or --oidc-extra-audience, and will reject tokens without a matching audience. Configure this in the Keycloak console under Clients → Client scopes → dedicated → Mappers.

Authorization options:

FlagExampleNotes
--allowed-rolemyroleRealm role
--allowed-rolemyclient:myroleClient role (uses <client-id>:<role-name> format)
--allowed-group/groupnameRequires "groups" client scope attached to client

Standard Keycloak installations include role mappers for realm and client roles via the pre-defined "roles" client scope . Group membership requires creating a separate "groups" client scope and attaching it to the client.

Other recommended settings: --code-challenge-method=S256 for PKCE, and scope = "openid email profile". See Keycloak OIDC provider docs for the full Keycloak client setup walkthrough.

ForwardAuth Integration with Traefik#

--reverse-proxy=true is required for all Traefik ForwardAuth setups so oauth2-proxy accepts and correctly uses X-Forwarded-* headers from Traefik .

There are two patterns:

Pattern 1 — ForwardAuth + errors middleware (redirect on 401)#

The forwardAuth middleware points at /oauth2/auth. oauth2-proxy returns 202 (authenticated) or 401 (unauthenticated) without proxying the request. The errors middleware catches 401–403 responses and redirects the browser to /oauth2/sign_in.

middlewares:
  oauth-auth:
    forwardAuth:
      address: https://oauth.example.com/oauth2/auth
      trustForwardHeader: true
  oauth-errors:
    errors:
      status: ["401-403"]
      service: oauth-backend
      query: "/oauth2/sign_in"

Pattern 2 — ForwardAuth to / with static://202 upstream (self-redirecting)#

oauth2-proxy is configured with --upstream=static://202. The forwardAuth middleware points to oauth2-proxy's / endpoint, which handles unauthenticated requests by redirecting directly to the sign-in flow — no separate errors middleware needed. A second middleware variant pointing at /oauth2/auth returns 401 without redirecting for routes where silent failure is preferred.

middlewares:
  oauth-auth-redirect: # auto-redirects to sign-in
    forwardAuth:
      address: https://oauth.example.com/
      trustForwardHeader: true
      authResponseHeaders:
        - X-Auth-Request-Access-Token
        - Authorization
  oauth-auth-wo-redirect: # returns 401 without redirect
    forwardAuth:
      address: https://oauth.example.com/oauth2/auth
      trustForwardHeader: true
      authResponseHeaders:
        - X-Auth-Request-Access-Token
        - Authorization

Header forwarding options:

FlagPurpose
--set-xauthrequestSets X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Groups response headers
--pass-authorization-headerForwards the OIDC ID token to upstream as Authorization: Bearer
--pass-access-tokenForwards OAuth access token via X-Forwarded-Access-Token; adds X-Auth-Request-Access-Token when combined with --set-xauthrequest

Gotcha — pass_host_header: defaults to true. Setting pass_host_header = false rewrites the Host header to the upstream URL, which breaks host-based routing inside Kubernetes .

Known Issue: CSRF Token Mismatch with Traefik#

When using Traefik's errors middleware with skip_provider_button = true, browsers with concurrent subresource requests (JS, CSS, service workers) cause Traefik to make multiple simultaneous calls to /oauth2/sign_in. Each call generates a new CSRF cookie that overwrites the previous one. By the time the OAuth callback arrives, the browser's CSRF cookie no longer matches the state parameter, resulting in a 403 "CSRF token mismatch" error .

Fix: enable cookie_csrf_per_request = true. This makes each authentication attempt write a unique CSRF cookie keyed by its state nonce rather than using a single shared _oauth2_proxy_csrf cookie, so concurrent requests each carry distinct cookies .

Related cookie options :

  • --cookie-csrf-expire — lifetime of CSRF cookies (default 15m)
  • --cookie-csrf-samesite — SameSite policy for CSRF cookies (independent of session cookie)
  • --cookie-csrf-per-request-limit — caps the number of concurrent CSRF cookies to avoid HTTP 431 errors

Alternative: set skip_provider_button = false. This makes /oauth2/sign_in serve a static HTML page (no cookies set), and CSRF state is only generated when the user clicks through to /oauth2/start — a single browser navigation .