diff --git a/pkg/op/session.go b/pkg/op/session.go index c4f76f32..90142555 100644 --- a/pkg/op/session.go +++ b/pkg/op/session.go @@ -34,12 +34,17 @@ func EndSession(w http.ResponseWriter, r *http.Request, ender SessionEnder) { RequestError(w, r, err) return } - err = ender.Storage().TerminateSession(r.Context(), session.UserID, session.ClientID) + redirect := session.RedirectURI + if fromRequest, ok := ender.Storage().(CanTerminateSessionFromRequest); ok { + redirect, err = fromRequest.TerminateSessionFromRequest(r.Context(), session) + } else { + err = ender.Storage().TerminateSession(r.Context(), session.UserID, session.ClientID) + } if err != nil { RequestError(w, r, oidc.DefaultToServerError(err, "error terminating session")) return } - http.Redirect(w, r, session.RedirectURI, http.StatusFound) + http.Redirect(w, r, redirect, http.StatusFound) } func ParseEndSessionRequest(r *http.Request, decoder httphelper.Decoder) (*oidc.EndSessionRequest, error) { @@ -60,11 +65,12 @@ func ValidateEndSessionRequest(ctx context.Context, req *oidc.EndSessionRequest, RedirectURI: ender.DefaultLogoutRedirectURI(), } if req.IdTokenHint != "" { - claims, err := VerifyIDTokenHint[*oidc.TokenClaims](ctx, req.IdTokenHint, ender.IDTokenHintVerifier(ctx)) + claims, err := VerifyIDTokenHint[*oidc.IDTokenClaims](ctx, req.IdTokenHint, ender.IDTokenHintVerifier(ctx)) if err != nil { return nil, oidc.ErrInvalidRequest().WithDescription("id_token_hint invalid").WithParent(err) } session.UserID = claims.GetSubject() + session.IDTokenHintClaims = claims if req.ClientID != "" && req.ClientID != claims.GetAuthorizedParty() { return nil, oidc.ErrInvalidRequest().WithDescription("client_id does not match azp of id_token_hint") } diff --git a/pkg/op/storage.go b/pkg/op/storage.go index 590c4a03..72b75e0b 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -62,6 +62,14 @@ type AuthStorage interface { KeySet(context.Context) ([]Key, error) } +// CanTerminateSessionFromRequest is an optional additional interface that may be implemented by +// implementors of Storage as an alternative to TerminateSession of the AuthStorage. +// It passes the complete parsed EndSessionRequest to the implementation, which allows access to additional data. +// It also allows to modify the uri, which will be used for redirection, (e.g. a UI where the user can consent to the logout) +type CanTerminateSessionFromRequest interface { + TerminateSessionFromRequest(ctx context.Context, endSessionRequest *EndSessionRequest) (string, error) +} + type ClientCredentialsStorage interface { ClientCredentials(ctx context.Context, clientID, clientSecret string) (Client, error) ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error) @@ -152,9 +160,10 @@ type StorageNotFoundError interface { } type EndSessionRequest struct { - UserID string - ClientID string - RedirectURI string + UserID string + ClientID string + IDTokenHintClaims *oidc.IDTokenClaims + RedirectURI string } var ErrDuplicateUserCode = errors.New("user code already exists")