SAML for SaaS Engineers

Russell Haering
8 min readMar 12, 2018

Two years ago we needed to build an Okta integration at ScaleFT, which meant we needed a SAML library written in Go. For various reasons we decided to build it ourselves, and although I ultimately convinced one of our team members to do much of the hard work (thanks Phoebe!) I somehow ended up with a reputation as the “SAML person”. Karma, perhaps. Now, whenever someone on the team has a problem with SAML they come to me.

This is my primer on SAML which will hopefully allow me - and any other “SAML people” out there - to more easily share this heavy burden.

Federated Authentication

A SAML flow has three parties:

  1. A User
  2. A Service Provider (abbreviated “SP”)
  3. An Identity Provider (abbreviated “IdP”)

Both the Service Provider and the Identity Provider are web-based services. For example, a user attempting to access a company wiki (a Service Provider) might be required to authenticate against Okta (an Identity Provider).

The goal of SAML is to enable the user to prove their identity to many Service Providers while maintaining a single set of credentials used to authenticate to the Identity Provider. In the context of an organization the ability to offer this proof of identity often also acts as a coarse-grained authorization: the Identity Provider is aware of each configured Service Provider, and will only allow users to authenticate to services they are permitted to access.

Importantly, we also don’t want the user to actually supply their credentials to the Service Provider. SAML acts as an abstraction layer which permits the Identity Provider to worry about how to establish the user’s identity and the Service Provider to focus on what they do well without ever seeing a static credential.

SAML Flow Overview

One of the interesting choices SAML makes is not to require that the Service Provider and Identity Provider be able to communicate directly. Instead data is relayed through the user’s web browser.

A typical “SP-initiated” SAML flow looks like:

  1. The user visits the Service Provider
  2. The Service Provider generates an AuthnRequest (“authentication request”)
  3. The AuthnRequest is relayed via the user’s browser to the Identity Provider
  4. The Identity Provider establishes the user’s identity. Typically this would mean requiring the user to authenticate, then storing a session cookie in the user’s browser so that if they perform another SAML flow within a short period of time they won’t (necessarily) be required to authenticate again.
  5. The IdP generates a Response. The Response is signed by the IdP.
  6. The Response is relayed via the user’s browser to the Service Provider
  7. The Service Provider verifies the signature on the response against a pre-configured X.509 certificate, confirms that the Response isn’t expired or otherwise invalid, then extracts information about the user from the Response.

The AuthnRequest and Response are each an XML document.

A modified version of this is an IdP-initiated flow. Usually these are initiated by the user browsing some kind of catalog of apps in the IdP. When the user (who is typically already authenticated) clicks on one the above flow runs starting from step 6.

Entity IDs

Both the Service Provider and the Identity Provider have a unique Entity Identifier, typically in the form of a URL. When configuring SAML authentication you have to tell each Entity the ID of the other. The terminology for these identifiers varies between implementations, terms you’ll often see include:

  1. Entity ID
  2. Issuer
  3. Audience (especially in reference to the SP Entity ID when configuring the IdP)

Note that in most SAML implementations these Entity IDs are not global to the SP or IdP, but instead are customer- or configuration-specific. For example when you configure ScaleFT to authenticate against Okta the SP Entity ID identifies the ScaleFT Team being configured and the other identifies the ScaleFT “App” which the user creates within Okta.

AuthnRequests

A very simple AuthnRequest might look like:

The three most important parts of this very simple request are:

  1. An Issuer which contains the Entity ID of the SP
  2. An AssertionConsumerServiceURL, which is the URL to which a Response should be sent
  3. A Destination which the URL to which the request will be sent

Other common features, not present in this example include:

  1. A NameIDPolicy which describes what type of NameID the IdP should include with a Response. We’ll dive more into NameIDs when exploring responses.
  2. A RequestedAuthnContext which requests that the IdP authenticate the user in a specific fashion, for example via password auth. These contexts are quite flexible; the SAML spec actually says “there is a theoretically infinite number of unique authentication contexts”. For now it is sufficient to know that they exist, and can cause problems if an SP requests a context that the IdP can’t support.

Responses

A SAML Response conveys much more information than an AuthnRequest. Let’s just dive in with an example:

I stripped out the signature (more on signing shortly), but even so… wow. Let’s break this down a little:

  1. First we have some response metadata, similar to that included with the request
  2. Next we have an Issuer. A SP must validate that this matches the IdP Entity ID it is configured to accept responses from (although an ideal IdP wouldn’t sign responses with an Issuer other than it’s own).
  3. A Status, in this case indicating success.
  4. An Assertion.

Assertions are really the interesting part of a SAML Response.

First of all, it is worth understanding that a Response might have multiple Assertions, and the IdP might have actually gotten those assertions from a different system entirely. Each Assertion has its own Issuer, expresses its own set of conditions which the SP must validate, and can optionally have its own signature. In practice though, a Response typically has a single Assertion element.

The Assertion contains:

  1. Again, some metadata
  2. An Issuer, which again the SP must validate.
  3. A Subject. Finally something which begins to serve our original purpose of authenticating someone. The Subject (specifically the NameID) identifies who the Assertion applies to. The main thing worth mentioning here is that SAML specifies a bunch of different possible NameID formats including email addresses, Windows user names, etc. One format worth being wary of is “transient” NameIDs, which are explicitly defined as temporary unique identifiers. If a Service Provider needs to be able to identify individuals across multiple sessions transient values are almost certainly unsuitable. The format which seems most suitable for most Service Providers is “persistent” which is a unique, opaque, persistent identifier for the subject, but be aware that a lot of IdPs just return some kind of user name (which is probably fine but isn’t actually opaque and might not be persistent).
  4. A Conditions element, which restricts the circumstances under which a Service Provider may use the Assertion. Again, SPs should be careful to validate these to ensure that they aren’t consuming an Assertion which is old or intended for a different SP.
  5. An AuthnStatement which indicates how and when the user was authenticated and how long the Service Provider may allow their session to last before re-authenticating them.
  6. Finally, an AttributeStatement containing attributes of the subject such as name, contact information, and sometimes group information.

Signatures

Because a SAML Response is relayed via the user it authenticates it is critical that any information trusted by the Service Provider be signed. SAML supports signing of the Response itself, signing of the inner Assertion elements, or both.

SAML also supports encrypted assertions so that the IdP can communicate sensitive information to the SP, but this is less common.

A typical AuthnRequest doesn’t convey any special privilege, so signing of these is optional and not widely implemented.

The XML Signature mechanism used to sign SAML attributes is complex and error prone, so worth spending a few words on.

In other contexts a typical approach to signing a document is to serialize the document to bytes, run those bytes through a hash function to produce a fixed-size digest, then sign that digest. Then the serialized document and the signature are sent to a recipient who is able to verify the signature then deserialize the document.

SAML’s signing scheme has a more complex objective: to sign an XML document, encode the signature as XML, then embed the signature into the document that was just signed. This is called an “enveloped” signature. The resulting document is intended to be passed about by XML libraries which may do things like embed it into another document (which may alter the namespace declarations on the originally signed element), inject comments or re-arrange the attributes of the signed element.

None of these changes alters the logical meaning of the document, but each of them would normally invalidate the original signature. Therefore, the signature encodes a series of transformations which must be applied to convert the received element back into a document which is identical, byte-for-byte, to the one which was originally signed. In a typical SAML signature there are two such transformations:

  1. The first indicates that the embedded signature should be removed.
  2. The second indicates a canonicalization (abbreviated “c14n”) algorithm which defines rules such as how to handle comments, how to order attributes and what to do about redundant namespace declarations. The signer will have applied the same canonicalization algorithm prior to signing the document.

There are several canonicalization algorithms in common use.

An XML document may contain multiple signatures each covering an individual element but not the whole document, and this is often the case in a SAML Response where an Assertion will often have a signature independent of (or instead of) a signature on the entire Response.

A good rule when handling signed XML documents is:

A valid signature should only be taken to imply the integrity of the result of the transformation process.

In other words, don’t validate the signature then trust the original input. Instead, the signature validation process should return the exact document, post-transformation against which the signature was ultimately validated, and you should trust only this document. If the signature was valid then that document will be exactly the value over which the signer actually computed the signature, so there is no opportunity for an attacker in the middle to affect the subsequent behavior of your code. Following this rule eliminates whole classes of common vulnerabilities including one discovered quite recently.

Bindings

There are two ways SAML typically relays XML documents through a user’s web browser:

  1. HTTP Redirect Binding — one service constructs a URL which points to the other service, embedding the document (possibly compressed, always base64ed) into a query parameter.
  2. HTTP POST Bindings — one service renders a web page containing an HTML form. The form has the document (again, possibly compressed, always base64ed) included as a hidden input and a URL pointing to the other service as the action of the form. Immediately after the page is loaded Javascript is used to submit the form.

The Redirect binding is simpler to implement, so is often used by Service Providers to relay an AuthnRequest to IdPs.

The POST binding is capable of relaying much larger documents, so is usually used by Identity Providers to relay a Response (which is usually much larger than an AuthnRequest) to SPs.

Takeaways

SAML is a powerful protocol with several advantages over protocols with similar objectives such as OpenID Connect:

  1. It is able to operate even when the Identity Provider and Service Provider are unable to communicate directly
  2. It offers many powerful features (for example RequestedAuthnContext) which aren’t standardized or widely deployed in other protocols
  3. It is very flexible and supports a wide variety of configurations

However SAML’s flexibility also makes it difficult to implement and troubleshoot.

Hopefully this guide will help others out there dealing with these challenges, and just maybe I can share the role of “SAML person” with my team.

--

--