diff --git a/docs/docs/features/endpoints.md b/docs/docs/features/endpoints.md index db00ba1e..08dee0bc 100644 --- a/docs/docs/features/endpoints.md +++ b/docs/docs/features/endpoints.md @@ -38,6 +38,20 @@ X-Auth-Request-Redirect: https://my-oidc-provider/sign_out_page BEWARE that the domain you want to redirect to (`my-oidc-provider.example.com` in the example) must be added to the [`--whitelist-domain`](../configuration/overview) configuration option otherwise the redirect will be ignored. Make sure to include the actual domain and port (if needed) and not the URL (e.g "localhost:8081" instead of "http://localhost:8081"). +ID Token can be injected in the redirect url by using `{id_token}` placeholder. For example to redirect to `https://my-oidc-provider.example.com/sign_out_page?id_token_hint={id_token}&post_logout_redirect_uri=https://my-app.example.com`; + +``` +/oauth2/sign_out?rd=https%3A%2F%2Fmy-oidc-provider.example.com%2Fsign_out_page%3Fid_token_hint%3D%7Bid_token%7D%26post_logout_redirect_uri%3Dhttps%3A%2F%2Fmy-app.example.com +``` + +or alternatively in the header: + +``` +GET /oauth2/sign_out HTTP/1.1 +X-Auth-Request-Redirect: https://my-oidc-provider.example.com/sign_out_page?id_token_hint={id_token}&post_logout_redirect_uri=https://my-app.example.com +... +``` + ### Auth This endpoint returns 202 Accepted response or a 401 Unauthorized response. diff --git a/oauthproxy.go b/oauthproxy.go index c6db18a7..836d6d22 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -54,6 +54,8 @@ const ( authOnlyPath = "/auth" userInfoPath = "/userinfo" staticPathPrefix = "/static/" + + idTokenPlaceholder = "{id_token}" ) var ( @@ -752,6 +754,15 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) { return } + if strings.Contains(redirect, idTokenPlaceholder) { + session, err := p.getAuthenticatedSession(rw, req) + if err != nil { + logger.Errorf("error getting authenticated session during SignOut, won't replace id_token placeholder in redirect URL: %v", err) + } else { + redirect = strings.ReplaceAll(redirect, idTokenPlaceholder, session.IDToken) + } + } + p.backendLogout(rw, req) http.Redirect(rw, req, redirect, http.StatusFound) @@ -773,7 +784,7 @@ func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request) { return } - backendLogoutURL := strings.ReplaceAll(providerData.BackendLogoutURL, "{id_token}", session.IDToken) + backendLogoutURL := strings.ReplaceAll(providerData.BackendLogoutURL, idTokenPlaceholder, 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 diff --git a/oauthproxy_test.go b/oauthproxy_test.go index ccabdbbd..bd8a6266 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" + middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth" @@ -3531,3 +3532,49 @@ func TestGetOAuthRedirectURI(t *testing.T) { }) } } + +func TestIdTokenPlaceholderInSignOut(t *testing.T) { + opts := baseTestOptions() + opts.WhitelistDomains = []string{"my-oidc-provider.example.com"} + + err := validation.Validate(opts) + assert.NoError(t, err) + + const emailAddress = "john.doe@example.com" + const userName = "9fcab5c9b889a557" + created := time.Now() + + session := &sessions.SessionState{ + User: userName, + Groups: []string{"a", "b"}, + Email: emailAddress, + IDToken: "eYjjjjjj.vvvv.ddd", + AccessToken: "oauth_token", + CreatedAt: &created, + } + + proxy, err := NewOAuthProxy(opts, func(email string) bool { + return true + }) + assert.NoError(t, err) + + // Save the required session + rw := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + err = proxy.sessionStore.Save(rw, req, session) + assert.NoError(t, err) + + rw = httptest.NewRecorder() + + rdUrl := url.QueryEscape("https://my-oidc-provider.example.com/sign_out_page?id_token_hint={id_token}&post_logout_redirect_uri=https://my-app.example.com/") + req, _ = http.NewRequest("GET", "/oauth2/sign_out?rd="+rdUrl, nil) + req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{ + RequestID: "11111111-2222-4333-8444-555555555555", + Session: session, + }) + + proxy.SignOut(rw, req) + newLocation := rw.Header().Values("Location")[0] + + assert.Equal(t, "https://idp.com/endsession?id_token_hint=eYjjjjjj.vvvv.ddd&post_logout_redirect_uri=http://myapp.com", newLocation) +}