Help with middleware

Hi all, I’m having difficulty understanding how middleware works.

I am trying to use this SAML authentication package https://github.com/crewjam/saml to implement a web application ‘Service Provider’ that works with a SAML bases single sign-on. The middleware works great except I need to make some modifications to it in my code to override the way it works.

What I need to do is create a handler in my code that replaces this function in samlsp/middleware.go

I want to replace it with…

func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/metadata" {
		m.ServeMetadata(w, r)
		return
	}

	if r.URL.Path == "/acs" {
		m.ServeACS(w, r)
		return
	}

	http.NotFoundHandler().ServeHTTP(w, r)
}

This is where I get a bit confused, I don’t understand why I can’t call the ServeACS and ServeMetadata functions from my own code, using something like samlsp.ServeACS.

I think I am getting a bit confused between the relationships of middleware and interfaces, both fairly advanced concepts compared to my rather rudimentary programming experience. I’m hoping that someone might be kind enough to provide an example of how I could override this in my own code.

Thanks

I think you don’t really need to replace the function.
Both ServiceProvider.MetadataURL and ServiceProvider.AcsURL are exported members of the middleware struct so you can modify/set them to achieve what you want.

	// Generate your middleware
	samlSP, _ := samlsp.New(samlsp.Options{
		// Whatever options you need to build to the middleware
	})

	// ServiceProvider.MetadataURL and ServiceProvider.AcsURL are both 
	// url.URL types from the net/url package. 
	// We need to represent /metadata and /acs using that type
	metadataURL, _ := url.Parse("/metadata")
	acsURL, _ := url.Parse("/acs")


	samlSP.ServiceProvider.MetadataURL = metadataURL
	samlSP.ServiceProvider.AcsURL = acsURL

	// Use the middleware
	http.Handle("/saml/", samlSP)	

Edit: Yup. According to the docs, you can simple change some members to modify the behavior: https://pkg.go.dev/github.com/crewjam/saml@v0.4.5/samlsp#New

You can customize the behavior of the middleware in more detail by replacing and/or changing Session, RequestTracker, and ServiceProvider in the returned Middleware.

Thanks for the advice Rolan but unfortunately I still need to override the function.

The reason for needing to change it is that it will be running behind a reverse proxy, so the browser will be requesting /saml/metadata but the proxy will be forwarding anything under /saml to my application. As a result my app only sees the http.request value of /metadata rather than /saml/metadata.

The problem is a mismatch in this code…

Basically now r.URL.Path is from the http.Request and = “/metadata” and m.ServiceProvider.MetadataURL.Path = “/saml/metadata”. so they never match.

Unfortunately changing samlSP.ServiceProvider.MetadataURL & samlSP.ServiceProvider.AcsURL means that the metadata XML file that contains the URLs would then be wrong, the external identity provider needs to read the XML file to find out where to send results to. Changing samlSP.ServiceProvider.AcsURL to “/acs” would change it in the XML file and then the external identity provider would be attempting to send a response to /acs instead of /saml/acs.

There is a closed issue on github https://github.com/crewjam/saml/issues/283 where serveMetadata and serveACS were made public by changing them to ServeMetadata and ServeACS to solve this problem. The recommendation being that “the user can add a handler for it manually”. My problem is that I can’t figure out any way to have my handler call ServeMetadata and ServeACS.

For the time being I am simply using their example /example/trivial/trivial.go and trying to change so that instead of http.Handle("/saml/", samlSP)
I want something along the lines of http.Handle("/metadata", samlSP. ServeMetadata()) but whatever I try I can’t seem to figure out how to use ServeMetadata in my own code.

Maybe you can try creating your own middleware struct that will embed the saml middleware struct. This was, your custom middleware will have access (“inherit”) the methods and fields of saml middleware. Then you can override saml middleware.ServerHTTP method with your own.

type MyMiddleware struct {
	*saml.Middleware
}
func (m *MyMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/metadata" {
		m.ServeMetadata(w, r)
		return
	}

	if r.URL.Path == "/acs" {
		m.ServeACS(w, r)
		return
	}

	http.NotFoundHandler().ServeHTTP(w, r)
}
samlSP, _ := samlsp.New(samlsp.Options{
		// Whatever options you need to build to the middleware
	})

m := &MyMiddleware{samlSP}
http.Handle("/saml/", m)
1 Like

Thanks again, I had tried something along those lines but got the error…

m.ServeMetadata undefined (type *MyMiddleware has no field or method ServeMetadata)compiler[MissingFieldOrMethod].

It turns out that I was making the stupid mistake of using the 4.5 release version of the library where the methods had not yet been changed from serveMetadata serveACS to ServeMetadata & ServeACS. I had assumed that it was my understanding that was wrong but you offering this example showed that I had already tried a similar solution that should have worked but didn’t. This prompted me to try and figure out why it wouldn’t work and when I realised that the change in methods had not yet been made!

I feel a bit embarrassed now for such a stupid oversight, but thanks for all your effort to help me troubleshoot it. It is so helpful to have someone else to provide some insight, it can be so difficult working on a project alone without anyone to ask when stuck with something. Your help is most appreciated, thanks so much for your time.

In the mean time I have created my own small middleware that takes in the http.Request, removes /saml from the path and then returns the ServeHTTP with the modified request. I’ll revisit it again when the library is updated.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.