From cf5d34acf60dc4e2a232e073bb906169be1dd32b Mon Sep 17 00:00:00 2001 From: Stefan Markmann <50301139+StefanMarkmann@users.noreply.github.com> Date: Sun, 18 Jan 2026 00:36:08 +0100 Subject: [PATCH] revert: "fix: skip provider button auth only redirect (#3309)" (#3314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 9c61c49ec29dce1c6bddfe209940bb406f42f8fc. The original fix broke nginx deployments using `auth_request`. When `/oauth2/auth` returns 302, nginx's `auth_request` module treats this as an internal error: [error] auth request unexpected status: 302 while sending to client nginx then returns **500 Internal Server Error** to the browser. > If the subrequest returns a 2xx response code, the access is allowed. If it returns 401 or 403, > the access is denied with the corresponding error code. Any other response code returned by the > subrequest is considered an error. https://nginx.org/en/docs/http/ngx_http_auth_request_module.html The nginx `auth_request` module has strict semantics (non-negotiable): | Subrequest status | nginx behavior | |---|---| | 2xx | Allow request | | 401 / 403 | Deny → trigger `error_page` | | **Any other status** | **Internal error → 500** | The `/oauth2/auth` endpoint is used as a **policy oracle** (yes/no decision), not as a browser-facing endpoint. It cannot return redirects. Any nginx deployment with: - `skip-provider-button=true` - Using `auth_request` directive Will receive 500 errors instead of the expected authentication flow. The correct fix for #334 is a **documentation update**, not a code change: ```nginx error_page 401 = @oauth2_signin; location @oauth2_signin { return 302 /oauth2/sign_in?rd=$scheme://$host$request_uri; } ``` This keeps `/oauth2/auth` as a pure 401/2xx oracle and lets nginx perform the proper 302 redirect to the browser. - Original Issue: #334 - Regression introduced in PR: #3309 Signed-off-by: Stefan Markmann Signed-off-by: Jan Larwig Co-authored-by: Jan Larwig --- CHANGELOG.md | 2 + docs/docs/configuration/overview.md | 2 +- .../version-7.14.x/configuration/overview.md | 2 +- oauthproxy.go | 10 +---- oauthproxy_test.go | 45 +------------------ 5 files changed, 7 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c21a99f..ab676530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ## Changes since v7.14.1 +- [#3314](https://github.com/oauth2-proxy/oauth2-proxy/pull/3314) revert: fix: skip provider button auth only redirect (#3309) + # V7.14.1 ## Release Highlights diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md index c5455336..7bd7bf07 100644 --- a/docs/docs/configuration/overview.md +++ b/docs/docs/configuration/overview.md @@ -214,7 +214,7 @@ Provider specific options can be found on their respective subpages. | flag: `--skip-auth-regex`
toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | | | flag: `--skip-auth-route`
toml: `skip_auth_routes` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR method!=path_regex. For all methods: path_regex OR !=path_regex | | | flag: `--skip-jwt-bearer-tokens`
toml: `skip_jwt_bearer_tokens` | bool | will skip requests that have verified JWT bearer tokens (the token must have [`aud`](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields) that matches this client id or one of the extras from `extra-jwt-issuers`) | false | -| flag: `--skip-provider-button`
toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start. When enabled, the `/oauth2/auth` endpoint returns 302 redirects instead of 401, ensuring compatibility with nginx `auth_request` and similar reverse proxy authentication architectures. | false | +| flag: `--skip-provider-button`
toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false | | flag: `--ssl-insecure-skip-verify`
toml: `ssl_insecure_skip_verify` | bool | skip validation of certificates presented when using HTTPS providers | false | | flag: `--trusted-ip`
toml: `trusted_ips` | string \| list | list of IPs or CIDR ranges to allow to bypass authentication (may be given multiple times). When combined with `--reverse-proxy` and optionally `--real-client-ip-header` this will evaluate the trust of the IP stored in an HTTP header by a reverse proxy rather than the layer-3/4 remote address. WARNING: trusting IPs has inherent security flaws, especially when obtaining the IP address from an HTTP header (reverse-proxy mode). Use this option only if you understand the risks and how to manage them. | | | flag: `--whitelist-domain`
toml: `whitelist_domains` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` or a `*.` to allow subdomains (e.g. `.example.com`, `*.example.com`) [^2] | | diff --git a/docs/versioned_docs/version-7.14.x/configuration/overview.md b/docs/versioned_docs/version-7.14.x/configuration/overview.md index c5455336..7bd7bf07 100644 --- a/docs/versioned_docs/version-7.14.x/configuration/overview.md +++ b/docs/versioned_docs/version-7.14.x/configuration/overview.md @@ -214,7 +214,7 @@ Provider specific options can be found on their respective subpages. | flag: `--skip-auth-regex`
toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | | | flag: `--skip-auth-route`
toml: `skip_auth_routes` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR method!=path_regex. For all methods: path_regex OR !=path_regex | | | flag: `--skip-jwt-bearer-tokens`
toml: `skip_jwt_bearer_tokens` | bool | will skip requests that have verified JWT bearer tokens (the token must have [`aud`](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields) that matches this client id or one of the extras from `extra-jwt-issuers`) | false | -| flag: `--skip-provider-button`
toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start. When enabled, the `/oauth2/auth` endpoint returns 302 redirects instead of 401, ensuring compatibility with nginx `auth_request` and similar reverse proxy authentication architectures. | false | +| flag: `--skip-provider-button`
toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false | | flag: `--ssl-insecure-skip-verify`
toml: `ssl_insecure_skip_verify` | bool | skip validation of certificates presented when using HTTPS providers | false | | flag: `--trusted-ip`
toml: `trusted_ips` | string \| list | list of IPs or CIDR ranges to allow to bypass authentication (may be given multiple times). When combined with `--reverse-proxy` and optionally `--real-client-ip-header` this will evaluate the trust of the IP stored in an HTTP header by a reverse proxy rather than the layer-3/4 remote address. WARNING: trusting IPs has inherent security flaws, especially when obtaining the IP address from an HTTP header (reverse-proxy mode). Use this option only if you understand the risks and how to manage them. | | | flag: `--whitelist-domain`
toml: `whitelist_domains` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` or a `*.` to allow subdomains (e.g. `.example.com`, `*.example.com`) [^2] | | diff --git a/oauthproxy.go b/oauthproxy.go index 8c8bc565..508084c8 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -988,15 +988,7 @@ func (p *OAuthProxy) enrichSessionState(ctx context.Context, s *sessionsapi.Sess // and optional authorization). func (p *OAuthProxy) AuthOnly(rw http.ResponseWriter, req *http.Request) { session, err := p.getAuthenticatedSession(rw, req) - // If SkipProviderButton is enabled and user needs login, redirect directly - // to OAuth provider instead of returning 401. This allows nginx auth_request - // to pass through the 302 redirect to the browser, bypassing error_page - // handling which can break redirect flows. - // See: https://github.com/oauth2-proxy/oauth2-proxy/issues/334 - if p.SkipProviderButton && err == ErrNeedsLogin { - p.doOAuthStart(rw, req, nil) - return - } else if err != nil { + if err != nil { http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 489a1b69..ccabdbbd 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -834,20 +834,8 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi return nil, err } testProvider := &TestProvider{ - ProviderData: &providers.ProviderData{ - ProviderName: "Test Provider", - LoginURL: &url.URL{ - Scheme: "http", - Host: "localhost", - Path: "/oauth/authorize", - }, - RedeemURL: &url.URL{ - Scheme: "http", - Host: "localhost", - Path: "/oauth/token", - }, - }, - ValidToken: opts.providerValidateCookieResponse, + ProviderData: &providers.ProviderData{}, + ValidToken: opts.providerValidateCookieResponse, } groups := pcTest.opts.Providers[0].AllowedGroups @@ -1124,35 +1112,6 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) { assert.Equal(t, "Unauthorized\n", string(bodyBytes)) } -// TestAuthOnlyEndpointRedirectWithSkipProviderButton tests that when SkipProviderButton -// is true and no session exists, the /auth endpoint should return a 302 redirect -// to the OAuth provider instead of 401. This is important for nginx auth_request -// architecture where 401 triggers error_page handling which can break the redirect flow. -// See: https://github.com/oauth2-proxy/oauth2-proxy/issues/334 -func TestAuthOnlyEndpointRedirectWithSkipProviderButton(t *testing.T) { - test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) { - opts.SkipProviderButton = true - }) - if err != nil { - t.Fatal(err) - } - - test.proxy.ServeHTTP(test.rw, test.req) - - // With SkipProviderButton=true and no session, should return 302 redirect - // to OAuth provider, NOT 401 Unauthorized - assert.Equal(t, http.StatusFound, test.rw.Code) - location := test.rw.Header().Get("Location") - assert.NotEmpty(t, location, "Expected Location header for redirect") - - // Verify the redirect points to the OAuth provider's authorize endpoint - // and contains key OAuth parameters - assert.Contains(t, location, "/oauth/authorize", "Expected redirect to OAuth authorize endpoint") - assert.Contains(t, location, "client_id=", "Expected client_id in redirect URL") - assert.Contains(t, location, "redirect_uri=", "Expected redirect_uri in redirect URL") - assert.Contains(t, location, "state=", "Expected state parameter in redirect URL") -} - func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) { test, err := NewAuthOnlyEndpointTest("", func(opts *options.Options) { opts.Cookie.Expire = time.Duration(24) * time.Hour