This commit is contained in:
vincent 2026-08-03 17:35:33 -07:00 committed by GitHub
commit 924605cee1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 3 deletions

View File

@ -8,6 +8,8 @@
## Changes since v7.15.3 ## Changes since v7.15.3
- [#3459](https://github.com/oauth2-proxy/oauth2-proxy/pull/3459) fix: include the provider's error_description on the OAuth2 callback error page (#3458) (@Molaire)
# V7.15.3 # V7.15.3
## Release Highlights ## Release Highlights

View File

@ -896,10 +896,21 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
} }
errorString := req.Form.Get("error") errorString := req.Form.Get("error")
if errorString != "" { if errorString != "" {
logger.Errorf("Error while parsing OAuth2 callback: %s", errorString) // Many providers (e.g. Okta, Entra ID, Keycloak) return a human-readable
// error_description alongside the error code. Surfacing it lets end users
// distinguish causes of an otherwise opaque code such as access_denied
// (for example "User is not assigned to the application" versus a policy
// denial) without needing access to the proxy logs.
errorDescription := req.Form.Get("error_description")
logger.Errorf("Error while parsing OAuth2 callback: %s %s", errorString, errorDescription)
message := fmt.Sprintf("Login Failed: The upstream identity provider returned an error: %s", errorString) message := fmt.Sprintf("Login Failed: The upstream identity provider returned an error: %s", errorString)
// Set the debug message and override the non debug message to be the same for this case if errorDescription != "" {
p.ErrorPage(rw, req, http.StatusForbidden, message, message) message = fmt.Sprintf("%s: %s", message, errorDescription)
}
// Set the debug message and override the non debug message to be the same
// for this case. The message is passed as an argument rather than a format
// string so a provider-supplied description containing '%' is rendered verbatim.
p.ErrorPage(rw, req, http.StatusForbidden, message, "%s", message)
return return
} }

View File

@ -843,6 +843,33 @@ func TestSignInPageSkipProviderDirect(t *testing.T) {
} }
} }
func TestOAuthCallbackProviderErrorIncludesDescription(t *testing.T) {
sipTest, err := NewSignInPageTest(false)
if err != nil {
t.Fatal(err)
}
// Okta (and other OIDC providers) return error_description alongside the
// error code when, for example, the user is not assigned the application.
code, body := sipTest.GetEndpoint(
"/oauth2/callback?error=access_denied&error_description=User+is+not+assigned+to+the+client+application.",
)
assert.Equal(t, http.StatusForbidden, code)
assert.Contains(t, body, "access_denied")
assert.Contains(t, body, "User is not assigned to the client application.")
}
func TestOAuthCallbackProviderErrorWithoutDescription(t *testing.T) {
sipTest, err := NewSignInPageTest(false)
if err != nil {
t.Fatal(err)
}
code, body := sipTest.GetEndpoint("/oauth2/callback?error=access_denied")
assert.Equal(t, http.StatusForbidden, code)
assert.Contains(t, body, "The upstream identity provider returned an error: access_denied")
}
type ProcessCookieTest struct { type ProcessCookieTest struct {
opts *options.Options opts *options.Options
proxy *OAuthProxy proxy *OAuthProxy