Skip to content

connector/saml: implement SP-Initiated Single Logout via LogoutCallbackConnector#4742

Open
Jabejixo wants to merge 4 commits into
dexidp:masterfrom
Jabejixo:saml-slo
Open

connector/saml: implement SP-Initiated Single Logout via LogoutCallbackConnector#4742
Jabejixo wants to merge 4 commits into
dexidp:masterfrom
Jabejixo:saml-slo

Conversation

@Jabejixo
Copy link
Copy Markdown
Contributor

Overview

Add SAML Single Logout (SLO) support to the SAML connector by implementing
the LogoutCallbackConnector interface from #4674.

What this PR does / why we need it

The SAML connector does not support Single Logout - when a user logs out
through Dex, the upstream IdP session stays active.

This PR adds two methods to the SAML connector:

  • LogoutURL - builds a <LogoutRequest> with HTTP-Redirect binding
    (DEFLATE + base64) using NameID and SessionIndex captured during login.
  • HandleLogoutCallback - validates the IdP's <LogoutResponse> for both
    HTTP-Redirect (GET) and HTTP-POST bindings with binding-aware signature
    validation.

New config fields: sloURL (IdP's SLO endpoint) and
insecureSkipSLOSignatureValidation. Existing ca/caData certificates
are reused - no additional configuration required.

Special notes for your reviewer

  • No changes to server-side code - uses the existing LogoutCallbackConnector
    interface called by logout.go.
  • NameID, NameIDFormat, and SessionIndex are persisted in ConnectorData
    during HandlePOST alongside the existing refresh token fields.
  • Fixed a latent bug: nameID.Format XML tag was missing attr — it is an
    attribute, not a child element.
  • HTTP-Redirect signature validation (Signature/SigAlg query params) is
    implemented separately from embedded XML signatures.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
@Jabejixo Jabejixo marked this pull request as ready for review April 15, 2026 07:01
Copy link
Copy Markdown
Member

@nabokihms nabokihms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we merge, two things I'd like us to sort out:

  1. The outgoing LogoutRequest isn't signed, and most IdPs reject unsigned ones by default.
  2. HandleLogoutCallback is missing a few checks that login-side already does — InResponseTo, Issuer, Destination. Without InResponseTo matching, a captured response could be replayed.

Left some other minor suggestions.

Comment thread connector/saml/saml.go Outdated
return fmt.Errorf("saml slo: %w", xrvErr)
}

if !p.insecureSkipSLOSignatureValidation {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few checks we do on the login side that I think we want here too:

  • InResponseTo - otherwise, a captured valid response could be replayed
  • Destination and Issuer — to prevent recipient/issuer confusion
  • Freshness on IssueInstant

InResponseTo is the important one — would need to stash the outgoing request ID in LogoutState. WDYT?

Comment thread connector/saml/saml.go Outdated
func newRequestID() string {
buf := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
panic("crypto/rand failed: " + err.Error())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: most of the connector returns errors rather than panicking. Mind making this (string, error) to match?

Comment thread connector/saml/saml.go
return "", nil
}

req := &logoutRequest{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most IdPs reject unsigned LogoutRequests in production. You've already got the redirect-binding sign logic for the verify path, so reusing it here should be straightforward. Could be a follow-up if we want to keep this PR focused, but probably worth flagging in the docs either way.

Did you get a chance to test against a real IdP? Curious if it accepted the unsigned request. Internally, we discussed that you tested with Keycloak, but...

Comment thread connector/saml/saml.go Outdated
nameIDPolicyFormat: c.NameIDPolicyFormat,

sloURL: c.SLOURL,
insecureSkipSLOSignatureValidation: c.InsecureSkipSLOSignatureValidation,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a case where someone trusts login signatures but not logout (or vice versa)? If not, maybe just reuse the existing InsecureSkipSignatureValidation? Two near-identically-named flags tend to cause support pain.

Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
Comment thread connector/saml/saml.go
Comment thread server/logout.go
Comment thread connector/saml/saml.go Outdated
Signed-off-by: Ivan Zvyagintsev <ivan.zvyagintsev@flant.com>
@Jabejixo Jabejixo requested a review from AlwxSin May 19, 2026 10:12
@Jabejixo Jabejixo requested a review from nabokihms May 20, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants