Skip to content

oauth2: add allowed_redirect_domains and original_request_uri#44282

Open
MohammedShetaya wants to merge 1 commit intoenvoyproxy:mainfrom
MohammedShetaya:oauth2-redirect-url
Open

oauth2: add allowed_redirect_domains and original_request_uri#44282
MohammedShetaya wants to merge 1 commit intoenvoyproxy:mainfrom
MohammedShetaya:oauth2-redirect-url

Conversation

@MohammedShetaya
Copy link
Copy Markdown

@MohammedShetaya MohammedShetaya commented Apr 4, 2026

When Envoy sits behind an external load balancer, the internal :authority and :scheme headers don't match the externally-visible hostname. The redirect_uri can already be templated with formatters to handle this, but the URL encoded in the OAuth2 state parameter is always derived from the request's :authority/:scheme — causing a mismatch that breaks the post-login redirect.

This PR adds two new config fields:

  • original_request_uri: an optional formatter-supported base URI (scheme + host)
    used to build the original request URL encoded into the OAuth2 state parameter.
    Operators can template it from x-forwarded-host/x-forwarded-proto so the
    post-auth redirect uses the public host rather than Envoy's internal :authority.
  • allowed_redirect_domains: a case-insensitive allowlist (exact or *. wildcard)
    applied to the host of the formatted redirect_uri, the formatted
    original_request_uri, and the URL decoded from the state parameter on callback.
    Mitigates open-redirect attacks via injected x-forwarded-host headers or forged
    state values. Empty list (default) disables the check for backward compatibility.

Risk Level: Low
Testing: Unit tests added for the allowlist (exact, wildcard, empty-list no-op), forwarded-header-driven redirect and state, and rejection on mismatch and on unparseable URLs.
Docs Changes: Proto field documentation added inline.
Release Notes: Added original_request_uri and allowed_redirect_domains options to the OAuth2 filter to support deployments behind external load balancers and prevent open redirects via the state parameter.
Platform Specific Features: N/A
[Optional Fixes #41034]
[Optional API Considerations: Two new fields added to OAuth2Config (field numbers 28 and 29). No breaking changes to existing configs.]

@repokitteh-read-only
Copy link
Copy Markdown

Hi @MohammedShetaya, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #44282 was opened by MohammedShetaya.

see: more, trace.

@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @wbpcode
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #44282 was opened by MohammedShetaya.

see: more, trace.

@phlax
Copy link
Copy Markdown
Member

phlax commented Apr 13, 2026

@MohammedShetaya needs main merge

@wbpcode gentle ping

Copy link
Copy Markdown
Member

@wbpcode wbpcode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for your great contribution. Sounds reasonable feature to me. One comment added to API.

Comment on lines +207 to +213
// When set to true, the host and scheme encoded in the OAuth2 state parameter
// will be derived from the configured redirect_uri instead of from the
// request's :authority and :scheme headers. This is useful when Envoy is
// deployed behind an external load balancer whose hostname differs from
// Envoy's internal hostname.
// Default is false.
bool match_redirect_url_to_redirect_uri = 28;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your requirements now. But I will preferred to add an optional original_uri configuration directly which would be much easier to understand the feature.

Although it brings minor additional overhead. But for oauth2, the performance, when redirect happens, is not that important.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wbpcode Thanks!
I applied the first approach because I didn't want to have a drift between the two values, but they service different purposes anyway. I believe it might cause some confusion, so I updated the code to implment original_request_uri

@wbpcode
Copy link
Copy Markdown
Member

wbpcode commented Apr 16, 2026

/wait

Copy link
Copy Markdown
Member

@wbpcode wbpcode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM to me now. Could you also add a change log for this new feature and also update the PR title.

I will inclined to merge this after the release of 1.38

@MohammedShetaya MohammedShetaya changed the title oauth2: add allowed redirect domain validation and option to derive state URL from redirect_uri oauth2: add allowed_redirect_domains and original_request_uri Apr 20, 2026
@MohammedShetaya MohammedShetaya requested a review from wbpcode April 20, 2026 14:12
@mathetake mathetake requested a review from bplotnick April 23, 2026 16:47
Adds two new fields to the OAuth2 filter config:

* `original_request_uri`: an optional formatter-supported base URI used to
  build the original request URL that is encoded into the OAuth2 `state`
  parameter. Useful when Envoy sits behind a gateway that terminates the
  user-facing hostname, so the post-authentication redirect uses the public
  host rather than Envoy's internal `:authority`.

* `allowed_redirect_domains`: an optional case-insensitive allow-list
  (exact match or `*.` wildcard) applied to the host of the formatted
  `redirect_uri`, the formatted `original_request_uri`, and the URL decoded
  from the `state` parameter on callback. Mitigates open-redirect attacks
  via injected `x-forwarded-host` headers or forged `state` values. Empty
  list (default) disables the check for backward compatibility.

The formatted `redirect_uri` and `original_request_uri` are now also
required to be parseable absolute URLs: an unparseable template output is
rejected with 401 rather than silently passing through the allow-list.
Authority parsing uses `Http::Utility::parseAuthority` so IPv6 literals
are handled consistently.

Signed-off-by: Mohammed Shetaya <mohammed.shetaya@procore.com>
@MohammedShetaya
Copy link
Copy Markdown
Author

Sorry for the noise. It automatically requested a review from almost everyone after a dirty commit.

Copy link
Copy Markdown
Member

@wbpcode wbpcode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for the update/contribution. The 1.38 was out and I only have one last comment. Thanks for update and waiting. 🙇

/wait

Comment on lines +992 to +996
Http::Utility::Url parsed_redirect_uri;
if (!parsed_redirect_uri.initialize(redirect_uri, false)) {
sendUnauthorizedResponse(fmt::format("redirect_uri is not a valid URL: {}", redirect_uri));
return;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From code's perspective, this is potential behavior change. Could you move this uri validation to the isHostAllowedDomain and only validate it when the allowed_redirect_domains is not empty? Then this how PR won't bring any un-guarded behavior change. Thanks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And you may could keep the following code in the previous position to make reviewing easier.

  // Format redirect_uri — needed for the query param sent to the identity provider
  auto formatter = THROW_OR_RETURN_VALUE(Formatter::FormatterImpl::create(config_->redirectUri()),
                                         Formatter::FormatterPtr);
  const auto redirect_uri = formatter->format({&headers}, decoder_callbacks_->streamInfo());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow configuring external host for OAuth2 filter state parameter

4 participants