How to implement SAML SSO in Golang
SAML in Go: getting started
If you’re reading this blog post, you probably (1) have a web application written in Go and (2) need to support SAML single sign-on (SSO). And, with rare exceptions, you’ve probably never worked with SAML before. That’s totally fine – this post will point you in the right direction.
Let’s start with some general resources. Unless you’re a SAML expert, you should probably acquaint yourself with what’s generally going on in SAML. Here are two articles we’ve written for you:
- A gentle introduction to SAML – this is a pretty loosely written, simplified overview of SAML. It won’t tell you everything you need to know, but it’ll help you make sense of the big picture
- SAML, a technical primer – this is a bit of a more technical exploration of SAML. It’ll cover a bunch of detail. You should probably read this if you want to really understand SAML.
Very broadly speaking, SAML sets up a standardized method for your software to authenticate users based on claims it receives from other software. Your customers will have software applications called identity providers (e.g., Microsoft Entra ID, Okta, etc). Users will log into the identity provider, which in turn relays their authentication status to you. You then log users in based on the message you get from the identity provider. (I’m glossing over a lot here.)
Setting up a SAML service is quite laborious and can be pretty dangerous. In this example app, we’ll instead use SSOReady as a middleware service to expedite, simplify, and secure our SAML implementation.
Here’s what SAML with SSOReady looks like in a sequence diagram:
(Fun fact for Go devs: SSOReady is open source and written in Go. You can checkout the GitHub here.)
Setting up the SSOReady SAML SDK for Go
SSOReady makes an SDK for Go; you can find it on GitHub here.
To install it, just run:
go get github.com/ssoready/ssoready-go
And then when you need to use it, you’ll just create an instance of the SSOReady client like this:
import (
"github.com/ssoready/ssoready-go"
ssoreadyclient "github.com/ssoready/ssoready-go/client"
)
ssoreadyClient := ssoreadyclient.NewClient()
SAML SSO in Go: an example app
Let’s just put together a maximally simple app. All of the code for this app lives on GitHub here. The GitHub has a better README than I’ve put together on this blog post, so take a glance over there if you’re following along!
You can also just clone the repo from the command line. Run the following:
git clone https://github.com/ssoready/ssoready-example-app-golang-saml
cd ssoready-example-app-golang-saml
go run .
We have two endpoints that matter: one that initiates SAML logins and another that processes SAML logins and establishes sessions.
We’ll call this first endpoint a SAML Redirect endpoint. It looks like this:
mux.HandleFunc("GET /saml-redirect", func(w http.ResponseWriter, r *http.Request) {
_, domain, _ := strings.Cut(r.URL.Query().Get("email"), "@")
getRedirectURLRes, err := ssoreadyClient.SAML.GetSAMLRedirectURL(r.Context(), &ssoready.GetSAMLRedirectURLRequest{
OrganizationExternalID: &domain,
}) // pretending for demo purposes that email domains are company IDs
if err != nil {
panic(err)
}
http.Redirect(w, r, *getRedirectURLRes.RedirectURL, http.StatusFound)
})
It’s just doing two things:
- It’s hitting the SSOReady API to get a redirect URL to your customer’s corporate identity provider (e.g., Microsoft Entra ID, Okta, etc.)
- It’s redirecting the user to visit that redirect URL
If that doesn’t make much sense to you, you should check out the more detailed explanations of SAML that I linked above. This’ll make more sense when you have a firmer handle of how SAML works.
In essence, you’re sending your user somewhere else to authenticate. When they successfully authenticate, they’ll end up back at your app – at the callback endpoint – with a saml_access_code
query parameter.
The callback endpoint exists to receive that redirect, get the SAML access code, exchange the access code for user details via SSOReady’s API, and then create a session for a user. It looks like this:
mux.HandleFunc("GET /ssoready-callback", func(w http.ResponseWriter, r *http.Request) {
samlAccessCode := r.URL.Query().Get("saml_access_code")
redeemRes, err := ssoreadyClient.SAML.RedeemSAMLAccessCode(r.Context(), &ssoready.RedeemSAMLAccessCodeRequest{
SAMLAccessCode: &samlAccessCode,
})
if err != nil {
panic(err)
}
// demo only: don't set your cookies like this!
http.SetCookie(w, &http.Cookie{
Name: "email",
Value: *redeemRes.Email,
})
http.Redirect(w, r, "/", http.StatusFound)
})
Note that we’re doing some silly things – because this is just a demo app – but your implementation in production can really look pretty simple! If you have those two endpoints in place, you’re pretty much done!
SAML SSO in Go: wrapping up
To recap, this app implements a pretty simple proof-of-concept for SAML single sign-on using SSOReady’s API. It’s just two endpoints: one to initiate SAML logins and another to accept them.
If you need to implement SAML SSO in your web app, you can use our Go SDK and free cloud service to finish up in a day or two. Plus, it’s totally open source (MIT) and written in Go. If you want to make a contribution, come join us on GitHub.
Use of the SSOReady middleware makes your SAML implementation much faster, easier, and tidier. It’s also safer and comes with some nice user experience bells and whistles (e.g., a self-serve configuration interface for your customers).