Skip to content

Allow configuring external host for OAuth2 filter state parameter #41034

@Schtil

Description

@Schtil

The envoy.filters.http.oauth2 filter currently generates the state parameter by encoding the original request URL (scheme://host/path).
See filter.cc#L846-L847.

This URL is always derived from the request’s internal :scheme and Host/:authority.
When Envoy is deployed behind an external load balancer, the internal Envoy only sees its internal hostname.
As a result, the state sent to the IdP contains the wrong domain, and after successful login the redirect fails.

For the redirect_uri this problem can be solved, because the filter allows configuring it with formatters.

For example:

redirect_uri: "%REQ(x-forwarded-proto?:scheme)%://%REQ(x-forwarded-host?:authority)%/auth/callback"

This way operators can combine forwarded headers with internal values to build the correct external URI.
However, there is no equivalent flexibility for the state parameter. It is always built from Host, with no option to prefer X-Forwarded-Host or align it with the configured redirect_uri.

Schema:

Image

Why this matters

  • In multi-LB setups the mismatch between redirect_uri and state breaks OAuth2 flows.

  • Operators are forced to add brittle Lua/Wasm filters to rewrite Host around the oauth2 filter just to trick it into producing the correct state.

  • A consistent configuration mechanism would allow both redirect_uri and state to be based on the same external domain.

Possible solutions

There are several ways this could be addressed, all with different trade-offs:

In all cases, there should also be a way to restrict allowed base domains (e.g. company.com, *.company.com) to avoid open redirect vulnerabilities.

  1. Global flag
    Add a simple option like:

    use_forwarded_headers: true
    allowed_domains:
       - example.com
       - "*.example.com"

    When enabled, both redirect_uri and state would use X-Forwarded-Host / X-Forwarded-Proto (if present), falling back to :authority / :scheme.
    Pro: very easy to configure, solves the most common case.
    Con: less flexibility.

  2. Сonfigurable state_uri with formatters
    Allow operators to explicitly configure how the state URL should be built, similar to redirect_uri:

    state_uri: "%REQ(x-forwarded-proto?:scheme)%://%REQ(x-forwarded-host?:authority)%"
    allowed_domains:
       - example.com
       - "*.example.com"

    Pro: maximum flexibility, consistent with redirect_uri.
    Con: more complex, risk of misconfiguring redirect_uri and state differently.

  3. Dedicated state_host_header / state_proto_header knobs
    Add targeted options like:

    state_host_header: x-forwarded-host    # default = Host/:authority
    state_proto_header: x-forwarded-proto  # default = :scheme
    allowed_domains:
       - example.com
       - "*.example.com"

    Pro: explicit, minimal changes.
    Con: adds more special-purpose fields to the filter config.


Any of these would solve the core problem: today redirect_uri can be fixed via formatters, but the state host and scheme are hardcoded to :authority/:scheme.

Benefits

  • Makes OAuth2 filter usable behind external LBs without hacks.

  • Provides consistency: both redirect_uri and state can reflect the same external domain.

  • Eliminates the need for custom Lua/Wasm filters that temporarily rewrite Host.

Alternatives today

  • Insert Lua/Wasm filter before and after oauth2 to rewrite Host/:authority.
    This workaround functions but is fragile, adds latency, and complicates configuration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/oauthenhancementFeature requests. Not bugs or questions.no stalebotDisables stalebot from closing an issue

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions