Skip to content

Commit 8711f75

Browse files
committed
Add Single Logout endpoint
1 parent 22de5ec commit 8711f75

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

identity_provider.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ type IdentityProvider struct {
110110
SignatureMethod string
111111
ValidDuration *time.Duration
112112
ResponseFormTemplate *template.Template
113+
SLOURL url.URL // SAML Single Logout URL
113114
}
114115

115116
// Metadata returns the metadata structure for this identity provider.
@@ -186,6 +187,23 @@ func (idp *IdentityProvider) Metadata() *EntityDescriptor {
186187
}
187188
}
188189

190+
// Add Single Logout Service endpoints to IDPSSODescriptor
191+
for i, idpSSODescriptor := range ed.IDPSSODescriptors {
192+
if idp.SLOURL.String() != "" {
193+
idpSSODescriptor.SingleLogoutServices = []Endpoint{
194+
{
195+
Binding: HTTPRedirectBinding,
196+
Location: idp.SLOURL.String(),
197+
},
198+
{
199+
Binding: HTTPPostBinding,
200+
Location: idp.SLOURL.String(),
201+
},
202+
}
203+
ed.IDPSSODescriptors[i] = idpSSODescriptor
204+
}
205+
}
206+
189207
return ed
190208
}
191209

samlidp/samlidp.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Options struct {
3030
//
3131
// /metadata - the SAML metadata
3232
// /sso - the SAML endpoint to initiate an authentication flow
33+
// /slo - the SAML endpoint for single logout
3334
// /login - prompt for a username and password if no session established
3435
// /login/:shortcut - kick off an IDP-initiated authentication flow
3536
// /services - RESTful interface to Service objects
@@ -54,6 +55,8 @@ func New(opts Options) (*Server, error) {
5455
metadataURL.Path += "/metadata"
5556
ssoURL := opts.URL
5657
ssoURL.Path += "/sso"
58+
sloURL := opts.URL
59+
sloURL.Path += "/slo"
5760
loginURL := opts.URL
5861
loginURL.Path += "/login"
5962
logr := opts.Logger
@@ -70,6 +73,7 @@ func New(opts Options) (*Server, error) {
7073
Certificate: opts.Certificate,
7174
MetadataURL: metadataURL,
7275
SSOURL: ssoURL,
76+
SLOURL: sloURL,
7377
LoginURL: loginURL,
7478
},
7579
logger: logr,
@@ -102,6 +106,7 @@ func (s *Server) InitializeHTTP() {
102106
mux.HandleFunc("/sso", func(w http.ResponseWriter, r *http.Request) {
103107
s.IDP.ServeSSO(w, r)
104108
})
109+
mux.HandleFunc("/slo", s.HandleSLO)
105110

106111
mux.HandleFunc("/login", s.HandleLogin)
107112
mux.HandleFunc("/login/{shortcut}", s.HandleIDPInitiated)

samlidp/session.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,40 @@ func (s *Server) HandleDeleteSession(w http.ResponseWriter, r *http.Request) {
201201
}
202202
w.WriteHeader(http.StatusNoContent)
203203
}
204+
205+
// HandleSLO handles the SAML Single Logout endpoint. It invalidates the user's session
206+
// and returns a simple confirmation page.
207+
func (s *Server) HandleSLO(w http.ResponseWriter, r *http.Request) {
208+
if err := r.ParseForm(); err != nil {
209+
s.logger.Printf("ERROR: Failed to parse form: %s", err)
210+
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
211+
return
212+
}
213+
214+
// Check for session cookie
215+
sessionCookie, err := r.Cookie("session")
216+
if err == nil {
217+
// Delete the session
218+
if err := s.Store.Delete(fmt.Sprintf("/sessions/%s", sessionCookie.Value)); err != nil {
219+
if err != ErrNotFound {
220+
s.logger.Printf("ERROR: Failed to delete session: %s", err)
221+
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
222+
return
223+
}
224+
}
225+
226+
// Clear the session cookie
227+
http.SetCookie(w, &http.Cookie{
228+
Name: "session",
229+
Value: "",
230+
MaxAge: -1,
231+
HttpOnly: true,
232+
Secure: r.URL.Scheme == "https",
233+
Path: "/",
234+
})
235+
}
236+
237+
// Return a simple logout confirmation page
238+
w.Header().Set("Content-Type", "text/html; charset=utf-8")
239+
w.Write([]byte("<html><body><h1>Logout Successful</h1><p>You have been logged out.</p></body></html>"))
240+
}

0 commit comments

Comments
 (0)