diff --git a/CHANGELOG.md b/CHANGELOG.md index 59682cd7..db5cd778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen) - [#2183](https://github.com/oauth2-proxy/oauth2-proxy/pull/2183) Allowing relative redirect url though an option (@axel7083) - [#1866](https://github.com/oauth2-proxy/oauth2-proxy/pull/1866) Add support for unix socker as upstream (@babs) +- [#1876](https://github.com/oauth2-proxy/oauth2-proxy/pull/1876) Add `--backend-logout-url` with `{id_token}` placeholder (@babs) - [#1949](https://github.com/oauth2-proxy/oauth2-proxy/pull/1949) Allow cookie names with dots in redis sessions (@miguelborges99) - [#2297](https://github.com/oauth2-proxy/oauth2-proxy/pull/2297) Add nightly build and push (@tuunit) - [#2329](https://github.com/oauth2-proxy/oauth2-proxy/pull/2329) Add an option to skip request to profile URL for resolving missing claims in id_token (@nilsgstrabo) diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 9c1ccdd9..2c49588f 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -440,6 +440,7 @@ Provider holds all configuration for a single provider | `scope` | _string_ | Scope is the OAuth scope specification | | `allowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group | | `code_challenge_method` | _string_ | The code challenge method | +| `backendLogoutURL` | _string_ | URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session | ### ProviderType #### (`string` alias) diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md index b822238b..346083f9 100644 --- a/docs/docs/configuration/overview.md +++ b/docs/docs/configuration/overview.md @@ -74,6 +74,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/ | `--auth-logging-format` | string | Template for authentication log lines | see [Logging Configuration](#logging-configuration) | | `--authenticated-emails-file` | string | authenticate against emails via file (one per line) | | | `--azure-tenant` | string | go to a tenant-specific or common (tenant-independent) endpoint. | `"common"` | +| `--backend-logout-url` | string | URL to perform backend logout, if you use `{id_token}` in the url it will be replaced by the actual `id_token` of the user session | | | `--basic-auth-password` | string | the password to set when passing the HTTP Basic Auth header | | | `--client-id` | string | the OAuth Client ID, e.g. `"123456.apps.googleusercontent.com"` | | | `--client-secret` | string | the OAuth Client Secret | | diff --git a/oauthproxy.go b/oauthproxy.go index 4784c578..982ed7d8 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -334,15 +334,15 @@ func (p *OAuthProxy) buildProxySubrouter(s *mux.Router) { s.Use(prepareNoCacheMiddleware) s.Path(signInPath).HandlerFunc(p.SignIn) - s.Path(signOutPath).HandlerFunc(p.SignOut) s.Path(oauthStartPath).HandlerFunc(p.OAuthStart) s.Path(oauthCallbackPath).HandlerFunc(p.OAuthCallback) // Static file paths s.PathPrefix(staticPathPrefix).Handler(http.StripPrefix(p.ProxyPrefix, http.FileServer(http.FS(staticFiles)))) - // The userinfo endpoint needs to load sessions before handling the request + // The userinfo and logout endpoints needs to load sessions before handling the request s.Path(userInfoPath).Handler(p.sessionChain.ThenFunc(p.UserInfo)) + s.Path(signOutPath).Handler(p.sessionChain.ThenFunc(p.SignOut)) } // buildPreAuthChain constructs a chain that should process every request before @@ -746,9 +746,43 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) { p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error()) return } + + p.backendLogout(rw, req) + http.Redirect(rw, req, redirect, http.StatusFound) } +func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request) { + session, err := p.getAuthenticatedSession(rw, req) + if err != nil { + logger.Errorf("error getting authenticated session during backend logout: %v", err) + return + } + + if session == nil { + return + } + + providerData := p.provider.Data() + if providerData.BackendLogoutURL == "" { + return + } + + backendLogoutURL := strings.ReplaceAll(providerData.BackendLogoutURL, "{id_token}", session.IDToken) + // security exception because URL is dynamic ({id_token} replacement) but + // base is not end-user provided but comes from configuration somewhat secure + resp, err := http.Get(backendLogoutURL) // #nosec G107 + if err != nil { + logger.Errorf("error while calling backend logout: %v", err) + return + } + + defer resp.Body.Close() + if resp.StatusCode != 200 { + logger.Errorf("error while calling backend logout url, returned error code %v", resp.StatusCode) + } +} + // OAuthStart starts the OAuth2 authentication flow func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) { // start the flow permitting login URL query parameters to be overridden from the request URL diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go index 6f032365..bc48e631 100644 --- a/pkg/apis/options/legacy_options.go +++ b/pkg/apis/options/legacy_options.go @@ -532,6 +532,7 @@ type LegacyProvider struct { UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"` AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"` AllowedRoles []string `flag:"allowed-role" cfg:"allowed_roles"` + BackendLogoutURL string `flag:"backend-logout-url" cfg:"backend_logout_url"` AcrValues string `flag:"acr-values" cfg:"acr_values"` JWTKey string `flag:"jwt-key" cfg:"jwt_key"` @@ -596,6 +597,7 @@ func legacyProviderFlagSet() *pflag.FlagSet { flagSet.String("user-id-claim", OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID") flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)") flagSet.StringSlice("allowed-role", []string{}, "(keycloak-oidc) restrict logins to members of these roles (may be given multiple times)") + flagSet.String("backend-logout-url", "", "url to perform a backend logout, {id_token} can be used as placeholder for the id_token") return flagSet } @@ -675,6 +677,7 @@ func (l *LegacyProvider) convert() (Providers, error) { Scope: l.Scope, AllowedGroups: l.AllowedGroups, CodeChallengeMethod: l.CodeChallengeMethod, + BackendLogoutURL: l.BackendLogoutURL, } // 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 9772a09e..f4e2839d 100644 --- a/pkg/apis/options/providers.go +++ b/pkg/apis/options/providers.go @@ -83,6 +83,9 @@ type Provider struct { AllowedGroups []string `json:"allowedGroups,omitempty"` // The code challenge method CodeChallengeMethod string `json:"code_challenge_method,omitempty"` + + // URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session + BackendLogoutURL string `json:"backendLogoutURL"` } // ProviderType is used to enumerate the different provider type options diff --git a/providers/provider_data.go b/providers/provider_data.go index a5fd7c2d..3c59c6da 100644 --- a/providers/provider_data.go +++ b/providers/provider_data.go @@ -57,6 +57,8 @@ type ProviderData struct { getAuthorizationHeaderFunc func(string) http.Header loginURLParameterDefaults url.Values loginURLParameterOverrides map[string]*regexp.Regexp + + BackendLogoutURL string } // Data returns the ProviderData diff --git a/providers/providers.go b/providers/providers.go index 1290642a..503b3559 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -159,6 +159,8 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData, p.setAllowedGroups(providerConfig.AllowedGroups) + p.BackendLogoutURL = providerConfig.BackendLogoutURL + return p, nil }