feat(openid): support logout_hint for RP-initiated logouts

This commit is contained in:
Trong Huu Nguyen
2026-01-16 09:18:30 +01:00
parent af0dfaa74c
commit 466d0132e9
2 changed files with 44 additions and 7 deletions

View File

@@ -13,12 +13,14 @@ import (
type Logout struct { type Logout struct {
*Client *Client
Cookie *openid.LogoutCookie Cookie *openid.LogoutCookie
logoutCallbackURL string LogoutHint string
callbackURL string
} }
func NewLogout(c *Client, r *http.Request) (*Logout, error) { func NewLogout(c *Client, r *http.Request) (*Logout, error) {
logoutCallbackURL, err := urlpkg.LogoutCallback(r) callbackURL, err := urlpkg.LogoutCallback(r)
if err != nil { if err != nil {
return nil, fmt.Errorf("generating logout callback url: %w", err) return nil, fmt.Errorf("generating logout callback url: %w", err)
} }
@@ -33,21 +35,25 @@ func NewLogout(c *Client, r *http.Request) (*Logout, error) {
} }
return &Logout{ return &Logout{
Client: c, Client: c,
Cookie: logoutCookie, Cookie: logoutCookie,
logoutCallbackURL: logoutCallbackURL, LogoutHint: r.URL.Query().Get("logout_hint"),
callbackURL: callbackURL,
}, nil }, nil
} }
func (in *Logout) SingleLogoutURL(idToken string) string { func (in *Logout) SingleLogoutURL(idToken string) string {
endSessionEndpoint := in.cfg.Provider().EndSessionEndpointURL() endSessionEndpoint := in.cfg.Provider().EndSessionEndpointURL()
v := endSessionEndpoint.Query() v := endSessionEndpoint.Query()
v.Set("post_logout_redirect_uri", in.logoutCallbackURL) v.Set("post_logout_redirect_uri", in.callbackURL)
v.Set("state", in.Cookie.State) v.Set("state", in.Cookie.State)
if len(idToken) > 0 { if len(idToken) > 0 {
v.Set("id_token_hint", idToken) v.Set("id_token_hint", idToken)
} }
if len(in.LogoutHint) > 0 {
v.Set("logout_hint", in.LogoutHint)
}
endSessionEndpoint.RawQuery = v.Encode() endSessionEndpoint.RawQuery = v.Encode()
return endSessionEndpoint.String() return endSessionEndpoint.String()

View File

@@ -70,6 +70,37 @@ func TestLogout_SingleLogoutURL(t *testing.T) {
logoutUrl.RawQuery = "" logoutUrl.RawQuery = ""
assert.Equal(t, EndSessionEndpoint, logoutUrl.String()) assert.Equal(t, EndSessionEndpoint, logoutUrl.String())
}) })
t.Run("with logout_hint claim", func(t *testing.T) {
logout := newLogout(t)
logout.LogoutHint = "some-logout-hint"
idToken := ""
state := logout.Cookie.State
raw := logout.SingleLogoutURL(idToken)
assert.NotEmpty(t, raw)
logoutUrl, err := url.Parse(raw)
assert.NoError(t, err)
query := logoutUrl.Query()
assert.Len(t, query, 3)
assert.Contains(t, query, "logout_hint")
assert.Equal(t, logout.LogoutHint, query.Get("logout_hint"))
assert.NotContains(t, query, "id_token_hint")
assert.Equal(t, idToken, query.Get("id_token_hint"))
assert.Contains(t, query, "post_logout_redirect_uri")
assert.Equal(t, LogoutCallbackURI, query.Get("post_logout_redirect_uri"))
assert.Contains(t, query, "state")
assert.Equal(t, state, query.Get("state"))
logoutUrl.RawQuery = ""
assert.Equal(t, EndSessionEndpoint, logoutUrl.String())
})
} }
func newLogout(t *testing.T) *client.Logout { func newLogout(t *testing.T) *client.Logout {