saml icon indicating copy to clipboard operation
saml copied to clipboard

Document SP initiated logout

Open chsclarke opened this issue 5 years ago • 6 comments

Please add an example of usage.

chsclarke avatar Oct 01 '20 23:10 chsclarke

Yes this would be extremely helpful.

schmidty avatar Nov 19 '20 21:11 schmidty

A pr on this would be most welcome. The support for logout is still pretty new at this time. Ref #307 and #308

crewjam avatar Dec 14 '20 17:12 crewjam

Any news on this? I am struggling with using this library and specially getting the logout working. I understand I need to somehow tell the IDP about the users logout request and also be able to answer IDPs logout request.

martinrode avatar Jul 12 '21 20:07 martinrode

Ok I have hacked together the first step of the Redirect:

type Logout struct {
	SP *samlsp.Middleware
}
func (l *Logout) ServeHTTP(w http.ResponseWriter, r *http.Request) {
       // THIS IS A HACK
      // the nameID is encoded in the `sub` field of the JWT, I am sending this as a query param 
      // but you should be able to get the JWT from the Cookie and then the subject from the JWT
	nameID := r.URL.Query().Get("name") 

	url, err := l.SP.ServiceProvider.MakeRedirectLogoutRequest(nameID, "relayState")
	if err != nil {
		WebErrorWarn("error get signouturl: "+err.Error(), http.StatusInternalServerError, w)
		return
	}

      //Note you need to set to set session domain so that the is does not contain the port 
     // will make another ticket on this 
	err = l.SP.Session.DeleteSession(w, r)
	if err != nil {
		WebErrorWarn("error get signouturl: "+err.Error(), http.StatusInternalServerError, w)
		return
	}

	http.Redirect(w, r, url.String(), http.StatusFound)
}

The problem is the logout response from the IDP is sent to the SloURL() which I believe does nothing right now(?).

It looks like I just need to:

  • register a handler for SloURL
  • extract the SAML message
  • Get the logoutRequestID from it
  • Redirect one last time saying it is all good

Can anyone verify these are the final steps? Provide any insight into getting the logoutRequestID?

upsampled avatar Oct 22 '21 13:10 upsampled

Ok I have gotten rid of the hack I used to get the session ID and got comfirmation that this is performing a good logout (the IDP is still keeping the user logged in but it at least closes the SP session early).

I am leaving the relay state blank as I just want the website to land on the Slo URL.

func (l *Logout) ServeHTTP(w http.ResponseWriter, r *http.Request) {
      //Get the JWT information
	session, err := l.SP.Session.GetSession(r)
	if err != nil {
		WebErrorWarn("error get signouturl session: "+err.Error(), http.StatusForbidden, w)
		return
	}
       //Get the JWT information part 2
	attr := session.(samlsp.JWTSessionClaims)
	if err != nil {
		WebErrorWarn("error get signouturl session claims: "+err.Error(), http.StatusForbidden, w)
		return
	}

	//use this as the name for the logout request
	url, err := l.SP.ServiceProvider.MakeRedirectLogoutRequest(attr.Subject, "")
	if err != nil {
		WebErrorWarn("error get signouturl: "+err.Error(), http.StatusInternalServerError, w)
		return
	}

	err = l.SP.Session.DeleteSession(w, r)
	if err != nil {
		WebErrorWarn("error get signouturl: "+err.Error(), http.StatusInternalServerError, w)
		return
	}

	http.Redirect(w, r, url.String(), http.StatusFound)
}

upsampled avatar Oct 29 '21 12:10 upsampled

I think the samlsp Middleware should include a handler to handle idp logout call back. Something like this:

in middleware.go

// ServeHTTP implements http.Handler and serves the SAML-specific HTTP endpoints
// on the URIs specified by m.ServiceProvider.MetadataURL and
// m.ServiceProvider.AcsURL.
func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// ... snip ...
        // add function to handle slo 
	if r.URL.Path == m.ServiceProvider.SloURL.Path {
		m.ServeSlo(w, r)
		return
	}
	// ...
}
// ...
// ServeSlo handles logout. It clears session and redirect to /
func (m *Middleware) ServeSlo(w http.ResponseWriter, r *http.Request) {
	err := m.Session.DeleteSession(w, r)
	if err != nil {
		m.OnError(w, r, err)
		return
	}
	http.Redirect(w, r, "/", http.StatusFound)
}

If we don't want to change the library, then the simplest way is to explicitly handle saml/slo route:

in an example main.go:

// assume we are using a global variable to represent the created sp middleware
var (
	samlSP *samlsp.Middleware
)
// ServeSlo handles logout. It clears session and redirect to /
func serveSlo(w http.ResponseWriter, r *http.Request) {
	err := samlSP.Session.DeleteSession(w, r)
	if err != nil {
		samlSP.OnError(w, r, err)
		return
	}
	http.Redirect(w, r, "/", http.StatusFound)
}

func main() {
    // ...
    samlSP, _ = samlsp.New(samlsp.Options{
        //...
        },
    })
    // ...
    http.HandleFunc("/saml/slo", serveSlo)
    //...
}

shaozi avatar Nov 11 '23 02:11 shaozi