To implement a Service Provider (SP) that delegates authentication to an Identity Provider (IDP), use the samlsp package.
Prerequisites
- X.509 Key Pair: You must have a self-signed X.509 key pair for the SP. You can generate one using OpenSSL:
openssl req -x509 -newkey rsa:2048 -keyout myservice.key -out myservice.cert -days 365 -nodes -subj "/CN=myservice.example.com"
- IDP Metadata: You need the metadata URL of the Identity Provider to fetch its configuration at startup.
Implementation Steps
- Load your X.509 certificate and private key.
- Use
samlsp.FetchMetadata to retrieve the IDP's metadata. - Initialize
samlsp.New with samlsp.Options containing your URL, Key, Certificate, and IDPMetadata. - Use
samlSP.RequireAccount(handler) to wrap protected endpoints. This ensures the user is authenticated before accessing the handler. - Register the
samlSP instance itself at the /saml/ path to handle SAML-specific protocol requests (like ACS and metadata).
Accessing User Attributes
You can retrieve user attributes (like displayName) from the request context using samlsp.AttributeFromContext(r.Context(), "attribute_name").
// ... imports
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", samlsp.AttributeFromContext(r.Context(), "displayName"))
}
func main() {
keyPair, err := tls.LoadX509KeyPair("myservice.cert", "myservice.key")
if err != nil {
panic(err)
}
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
if err != nil {
panic(err)
}
idpMetadataURL, err := url.Parse("https://samltest.id/saml/idp")
if err != nil {
panic(err)
}
idpMetadata, err := samlsp.FetchMetadata(context.Background(), http.DefaultClient, *idpMetadataURL)
if err != nil {
panic(err)
}
rootURL, err := url.Parse("http://localhost:8000")
if err != nil {
panic(err)
}
samlSP, _ := samlsp.New(samlsp.Options{
URL: *rootURL,
Key: keyPair.PrivateKey.(*rsa.PrivateKey),
Certificate: keyPair.Leaf,
IDPMetadata: idpMetadata,
})
app := http.HandlerFunc(hello)
http.Handle("/hello", samlSP.RequireAccount(app))
http.Handle("/saml/", samlSP)
http.ListenAndServe(":8000", nil)
}