If you’ve ever wondered about how the magic of SAML works behind the scenes, you might be in the right place. In this blog post, we’ll analyze requests and responses of the SAML federation that we set up in the previous episode of this series.
Note: this post is part of a series dedicated to AWS IAM Identity Center (a.k.a. IAM IC) and related integrations. Check out all the “episodes” following the tag #aws-auth-series.
Table of contents
Open Table of contents
SAML, how it works
SAML is a common protocol for authentication and authorization. It’s often used to enable Single Sign-On (SSO), and it’s extremely popular at the enterprise level. With SSO a user can utilize one set of credentials to log in to many applications. If you are not familiar with SAML, I recommend “A Developer’s Guide to SAML”, a very cool YouTube video introducing SAML and related concepts.
In this post, we are going to explore a Service Provider-initiated flow. In this scenario, the Service Provider (SP) starts the authentication process, then it relies on the Identity Provider (IdP) to federate the authentication.
0 - The trust
First things first: we need to establish trust between IAM Identity Center and our app. Let’s start by configuring the IAM Identity Center as described in the previous blog post. After that, clone this repo, and follow the instructions to get it up and running.
Next, you can download a browser extension for isolating the SAML traffic, such as:
- SAML-tracer (Chrome)
- SAML-tracer (Firefox)
While this extension is not a strict requirement, using a SAML-oriented extension can help filter SAML traffic and gather relevant information. It’s much more convenient than inspecting all the network traffic with the browser. Just remember to open the extension before you start the SAML authentication process.
Ok, cool: we are ready to start!
1. The user tries to access the app
The SP-initiated process begins with a user who wants to access to our Node app, namely the Service Provider. The user requests a protected app URL with her browser, such as http://localhost:3000. Our app receives this request, and sends back a redirection to the local login page. The user’s browser follows the redirection information, and it tries to get the URL http://localhost:3000/login.
2. The user is redirected to AWS
When the user’s browser requests the /login
path, a fundamental
redirection happens. Let’s look inside it with curl
:
$ curl -I http://localhost:3000/login
HTTP/1.1 302 Found X-Powered-By:
Express Location:
https://<IAM_IDENTITY_CENTER_ENTRYPOINT_URL>?SAMLRequest=<SAMLRequest>
...
Our app has just generated a SAML authentication request, and it is nothing
but a redirection to the AWS URL we set for our Service Provider with a
URL-encoded SAML Request, that is, in turn, encoded into XML
. You can
inspect the XML request with the SAML-tracer. Alternatively, you can decode it
manually with tools like
this.
Here’s what a SAMLRequest
looks like:
<samlp:AuthnRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_eb921ccc0cfdd887bf248173a9440ca09cd13840"
Version="2.0"
IssueInstant="2023-12-10T11:03:55.909Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Destination="https://portal.sso.eu-central-1.amazonaws.com/saml/assertion/foo"
AssertionConsumerServiceURL="http://localhost:3000/login/callback">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
http://localhost:3000/myDemoSAMLAudience
</saml:Issuer>
<samlp:NameIDPolicy
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
AllowCreate="true"
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
<samlp:RequestedAuthnContext
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
Comparison="exact">
<saml:AuthnContextClassRef
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
Let’s go over the elements of that request:
ID
: unique identifier for the request generated by our Node appVersion
: SAML version used (2.0 in this case)IssueInstant
: the timestamp of this requestDestination
: specifies the endpoint where the request should be sentIAM Identity Center sign-in URL
on the AWS side
AssertionConsumerServiceURL
: is the callback URL of the SP intended to handle the SAML response from the IAM IC- Application ACS URL on the AWS side
<saml:Issuer>
: is the predefined string that we set in the trust process- Application SAML audience on the AWS side
From the user’s perspective, she is just redirected to the log in page of the IAM Identity Center
3. The IdP Response
If the user logs in correctly to IAM IC with her username and password, the IdP generates a SAML response with information about the user and the authentication process. The XML of the SAML response is more complex than the requests. Moreover, part of the information needs to be signed. It couldn’t be easy to grasp relevant parts at the first sight. The response, among many other things, contains:
_id
: unique identifier for the response generated by IAM Identity CenterInResponseTo
: a reference to the requestID
Assertion
: an element containing data about the userSubject
: a reference to the user that can be used by our App to identify the user who wants to log in (in our case the user email, see the attribute mappings in IAM IC)AttributeStatement
: a wrapper element containing the other user attributes defined in the attribute mappings in IAM ICConditions
: the validity period of this SAML response
For the sake of convenience, we can inspect a quick summary under the Summary tab of the SAML-tracer. Still curious? We can explore the full XML under the SAML tab. I recommend again “A Developer’s Guide to SAML” as a reference for an in-depth analysis.
This response is routed back to the callback URL of our app. In this case,
there is not a redirection at the HTTP level, but IAM IC uses a mechanism
similar to an HTML form submission, with some custom JavaScript kicking in. The
final result is an HTTP POST request to http://localhost:3000/login/callback
,
with the SAML response in the body.
4. Logged in
Finally, the SP receives and validates the SAML response. If everything looks fine to our toy app, the user is logged in, and a dedicated session is established. In the last part of our example, the user is redirected once again to the requested protected content:
$ curl -v 'http://localhost:3000/login/callback' \
-H 'Content-Type: application/x-www-form-urlencoded' \
... \
--data-raw 'SAMLResponse=<SAMLResponse>
< HTTP/1.1 302 Found
< X-Powered-By: Express
< Location: /
...
Wrapping up
In this example, we have explored what happens under the hood when we use IAM Identity Center as the Identity Provider for our custom app.
Note that, usually, the entire process takes place within the user’s browser. The communication between the IdP and the SP is kind of like indirect and involves the user’s browser as an intermediary, routing information back and forth. This means that our SP can be hosted in a private network, without being directly reachable by the Identity Provider over the Internet.
The following picture (credit: IBM) summarizes well the SAML dance steps we have just executed.
There are many other flavors of SAML out there. I hope that this example can help you understand the actors involved and their responsibilities.
Cheers!
^..^