diff --git a/CHANGELOG.md b/CHANGELOG.md
index e02ee04de4..a9641d90c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+### 0.5.1 (upcoming)
+
+* [PLT-3581] Add configurable sign-out URL via `--sign-out-url` / `OAUTH2_PROXY_SIGN_OUT_URL` to support non-standard IdP logout flows (e.g. Autentica/REDSARA)
+
+## Previous development
+
### 0.5.0 (2026-05-11)
* [PLT-2583] Bump oauth2-proxy upstream to v7.15.2 (security fixes: authentication bypasses CVE-2026-34986, CVE-2026-32281 and others)
diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md
index 680741bad5..5a77c4ba0d 100644
--- a/docs/docs/configuration/alpha_config.md
+++ b/docs/docs/configuration/alpha_config.md
@@ -574,6 +574,7 @@ Provider holds all configuration for a single provider
| `googleConfig` | _[GoogleOptions](#googleoptions)_ | GoogleConfig holds all configurations for Google provider. |
| `oidcConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider
or providers utilize OIDC configurations. |
| `loginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. |
+| `sisConfig` | _[SISOptions](#sisoptions)_ | SISConfig holds all configurations for SIS provider. |
| `id` | _string_ | ID should be a unique identifier for the provider.
This value is required for all providers. |
| `provider` | _[ProviderType](#providertype)_ | Type is the OAuth provider
must be set from the supported providers group,
otherwise 'Google' is set as default |
| `name` | _string_ | Name is the providers display name
if set, it will be shown to the users in the login page. |
@@ -592,6 +593,7 @@ Provider holds all configuration for a single provider
| `code_challenge_method` | _string_ | The code challenge method |
| `additionalClaims` | _[]string_ | Additional claims to be obtained from the upstream IDP, either from the id_token or from the userinfo endpoint if configured. |
| `backendLogoutURL` | _string_ | URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session |
+| `signOutURL` | _string_ | SignOutURL overrides the provider's default sign-out redirect URL |
### ProviderType
#### (`string` alias)
@@ -615,6 +617,17 @@ AlphaConfig](https://oauth2-proxy.github.io/oauth2-proxy/configuration/alpha-con
However, [**the feature to implement multiple providers is not
complete**](https://github.com/oauth2-proxy/oauth2-proxy/issues/926).
+### SISOptions
+
+(**Appears on:** [Provider](#provider))
+
+
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `SISRootURL` | _string_ | SISRootURL is the OpenID Connect SISRoot URL |
+| `ClearExtraCookieNames` | _[]string_ | ClearExtraCookieNames sets cookie names to clear after sign out |
+
### SecretSource
(**Appears on:** [ClaimSource](#claimsource), [HeaderValue](#headervalue), [TLS](#tls))
diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go
index ff7668b576..48f3996eb6 100644
--- a/pkg/apis/options/legacy_options.go
+++ b/pkg/apis/options/legacy_options.go
@@ -530,6 +530,7 @@ type LegacyProvider struct {
GoogleAdminAPIUserScope string `flag:"google-admin-api-user-scope" cfg:"google_admin_api_user_scope"`
SISRootURL string `flag:"sis-root-url" cfg:"sis_root_url"`
+ SignOutURL string `flag:"sign-out-url" cfg:"sign_out_url"`
ClearExtraCookieNames []string `flag:"clear-extra-cookie-names" cfg:"clear_extra_cookie_names"`
// These options allow for other providers besides Google, with
@@ -720,6 +721,7 @@ func (l *LegacyProvider) convert() (Providers, error) {
CodeChallengeMethod: l.CodeChallengeMethod,
BackendLogoutURL: l.BackendLogoutURL,
AuthRequestResponseMode: l.AuthRequestResponseMode,
+ SignOutURL: l.SignOutURL,
}
// This part is out of the switch section for all providers that support OIDC
diff --git a/pkg/apis/options/providers.go b/pkg/apis/options/providers.go
index a04dc2b37f..6fb71c69f8 100644
--- a/pkg/apis/options/providers.go
+++ b/pkg/apis/options/providers.go
@@ -141,6 +141,8 @@ type Provider struct {
// URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session
BackendLogoutURL string `yaml:"backendLogoutURL"`
+ // SignOutURL overrides the provider's default sign-out redirect URL
+ SignOutURL string `yaml:"signOutURL,omitempty"`
}
// ProviderType is used to enumerate the different provider type options
diff --git a/providers/providers.go b/providers/providers.go
index 7e470ef691..7767fb3df9 100644
--- a/providers/providers.go
+++ b/providers/providers.go
@@ -137,6 +137,7 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
"profile": {dst: &p.ProfileURL, raw: providerConfig.ProfileURL},
"validate": {dst: &p.ValidateURL, raw: providerConfig.ValidateURL},
"resource": {dst: &p.ProtectedResource, raw: providerConfig.ProtectedResource},
+ "signout": {dst: &p.SignOutURL, raw: providerConfig.SignOutURL},
} {
var err error
*u.dst, err = url.Parse(u.raw)
diff --git a/providers/sis.go b/providers/sis.go
index 1c6163716a..dc9316ddf1 100644
--- a/providers/sis.go
+++ b/providers/sis.go
@@ -274,8 +274,8 @@ func (p *SISProvider) GetSignOutURL(redirectURI string) string {
// copy URL
redirect := *p.SignOutURL
if redirectURI != "" {
- v := url.Values{}
- v.Add("rd", redirectURI)
+ v := redirect.Query()
+ v.Set("rd", redirectURI)
redirect.RawQuery = v.Encode()
}
return redirect.String()
diff --git a/providers/sis_test.go b/providers/sis_test.go
index 0f305f59cd..329762926b 100644
--- a/providers/sis_test.go
+++ b/providers/sis_test.go
@@ -61,6 +61,41 @@ func TestSISProviderOverrides(t *testing.T) {
assert.Equal(t, "profile", p.Data().Scope)
}
+func TestSISProviderGetSignOutURL(t *testing.T) {
+ tests := []struct {
+ name string
+ signOutURL string
+ redirectURI string
+ expected string
+ }{
+ {
+ name: "no redirect preserves sign-out URL as-is",
+ signOutURL: "https://sis.example.com/sso/logout",
+ redirectURI: "",
+ expected: "https://sis.example.com/sso/logout",
+ },
+ {
+ name: "redirect appended as rd param",
+ signOutURL: "https://sis.example.com/sso/logout",
+ redirectURI: "https://app.example.com/home",
+ expected: "https://sis.example.com/sso/logout?rd=https%3A%2F%2Fapp.example.com%2Fhome",
+ },
+ {
+ name: "existing query params preserved when adding rd",
+ signOutURL: "https://autentica.example.com/logout?appId=5784",
+ redirectURI: "https://app.example.com/home",
+ expected: "https://autentica.example.com/logout?appId=5784&rd=https%3A%2F%2Fapp.example.com%2Fhome",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ u, _ := url.Parse(tt.signOutURL)
+ p := NewSISProvider(&ProviderData{SignOutURL: u}, options.SISOptions{})
+ assert.Equal(t, tt.expected, p.GetSignOutURL(tt.redirectURI))
+ })
+ }
+}
+
func TestSISProviderRedeem(t *testing.T) {
b := testSISBackend(map[string]string{
"/sso/oauth2.0/accessToken": "access_token=imaginary_access_token&expires=10000",