Web-Security Basics

SAML Decoded

The only guide you'll ever need for SAML.

What is SAML?

SAML stands for Security Assertion Markup Language. It's an open standard (XML-based) for exchanging authentication and authorization data between parties, specifically between an identity provider and a service provider.

The core problem SAML solves: How can App B trust that you've already logged into App A, without App B ever seeing your password?

๐Ÿจ The Hotel Key Analogy

Imagine you check into a hotel (the Identity Provider). The receptionist verifies your passport, then gives you a key card. Now you can access the gym, the pool, the restaurant, all without showing your passport again. Each service just reads your key card.

SAML is the key card system. The assertion is the key card. The hotel is your company's SSO. The gym, pool, and restaurant are Salesforce, Slack, and GitHub.

SAML enables Single Sign-On (SSO): log in once, access many applications. No repeated passwords. No app-by-app accounts. Centralized control.

๐Ÿ“… SAML 2.0 was ratified in 2005 by OASIS. It remains the dominant enterprise SSO standard, widely used in corporate environments alongside newer alternatives like OAuth 2.0 and OIDC.

The Three Actors

๐Ÿง‘โ€๐Ÿ’ป
User / Principal
aka "Subject"
The human who wants access. Typically uses a browser. They're the reason any of this exists, everything else serves them.
๐Ÿข
Service Provider
SP, e.g. Salesforce, GitHub, Slack
The app the user wants to access. It trusts the IdP to vouch for users. It never sees passwords, only assertions.
๐Ÿ”
Identity Provider
IdP, e.g. Okta, Azure AD, ADFS
The authority on who you are. It holds credentials, verifies identity, and issues signed assertions. The source of trust.
๐Ÿบ The Nightclub Analogy

You (User) want to enter a club (SP). The bouncer (SP) doesn't know you personally. But your government (IdP) issued you a passport. The bouncer trusts the government, checks the stamp, and lets you in. The bouncer never needed to call the government directly, the signed passport is the proof.

The Full SAML Flow

Click any step to expand the detail. This is SP-initiated flow (most common).

SP-Initiated SAML 2.0 Flow
1
User
Visits the Service Provider
User tries to access app.salesforce.com/dashboard without being logged in.
The SP detects there's no valid session cookie. It needs to figure out who this person is before granting access. It will redirect to the IdP for authentication.
2
Service Provider
Generates an AuthnRequest
The SP builds a signed XML document saying "I need this user authenticated." This is the AuthnRequest.
The AuthnRequest contains: the SP's entity ID, the requested assertion consumer URL (where IdP should send the response), timestamp, and a unique request ID to prevent replay attacks. It's often Base64-encoded and URL-encoded.
3
Service Provider โ†’ Browser
Redirects User to IdP
The SP sends a 302 Redirect to the browser pointing at the IdP's SSO URL, with the AuthnRequest embedded in the query string.
Example redirect URL: https://idp.okta.com/sso/saml?SAMLRequest=PHNhbWxwOi...&RelayState=/dashboard

The RelayState is a bookmark, it remembers where the user was trying to go, so after login they land in the right place.
4
User โ†’ Identity Provider
User Authenticates at the IdP
The browser follows the redirect. The user sees the IdP's login page (e.g. Okta). They enter credentials, do MFA, ... whatever the IdP requires.
The SP has zero involvement in this step. It never sees the password. The IdP handles the entire authentication process. If the user already has an active IdP session, this step is skipped entirely: that's the SSO magic!
5
Identity Provider
Issues a SAML Assertion
Authentication succeeds. The IdP creates a signed XML document, the SAML Assertion, vouching for the user's identity.
The assertion includes: who the user is (NameID), when they authenticated, how long the assertion is valid, their attributes (email, groups, roles), and a digital signature using the IdP's private key. The SP will verify this signature using the IdP's public certificate.
6
Identity Provider โ†’ Browser
POSTs Assertion to SP via Browser
The IdP returns an HTML form that auto-submits a POST request back to the SP's Assertion Consumer Service (ACS) URL. The browser is the carrier.
This is the HTTP-POST binding. The browser acts as a relay: it receives an HTML page containing a hidden form field with the Base64-encoded SAMLResponse, then immediately auto-submits it to the SP. The user barely sees this happen (it's instant).
7
Service Provider
Validates the Assertion
The SP receives the POST, decodes the SAMLResponse, and performs a series of critical security checks.
SP checks: โ‘  Signature is valid (using IdP's public cert) โ‘ก Assertion hasn't expired โ‘ข Audience matches this SP's entity ID โ‘ฃ InResponseTo matches the request ID it sent โ‘ค NotBefore/NotOnOrAfter timestamps are within tolerance. If any check fails โ†’ rejected.
โœ“
Service Provider โ†’ User
Access Granted!
The SP creates a local session, extracts user attributes from the assertion, and redirects the user to their original destination (from RelayState).
The SP may now provision the user (create an account if first login), assign roles based on group attributes, and set a session cookie. The whole redirect-login-redirect took under 2 seconds. The user never typed a password into Salesforce.

Key Concepts Explained

Click any card to see a concrete example.

Identity
NameID
The identifier the IdP uses to tell the SP who the user is. Can be an email, a persistent opaque ID, or a transient ID.
๐Ÿ“ง emailAddress: alice@company.com
๐Ÿ”’ persistent: _abc123def456... (opaque, stable)
๐ŸŽฒ transient: changes every session (privacy)
XML Document
Assertion
The core payload. Contains three statement types: Authentication (you logged in), Authorization (what you can do), and Attribute (your properties).
An assertion might say: "Alice authenticated at 14:30 UTC via MFA, her email is alice@co.com, and she belongs to the 'admins' group."
XML Element
Attribute Statement
Key-value pairs carried inside the assertion. The SP reads these to learn things about the user beyond just their identity.
Common attributes: email, firstName, lastName, groups, department, role. These drive authorization inside the SP.
Security
Digital Signature
The IdP signs the assertion with its private key. The SP verifies with the IdP's public certificate. This is what makes trust possible.
If a man-in-the-middle tampers with the assertion XML, the signature verification will fail. The SP rejects it. No forged assertions possible (if configured correctly).
Config
Entity ID
A unique URI that identifies each party (SP or IdP). Used to look up metadata. Not necessarily a real URL โ€” just a globally unique string.
SP: https://app.salesforce.com
IdP: https://company.okta.com/app/saml
Endpoint
ACS URL
Assertion Consumer Service URL: the SP endpoint that receives the SAML response from the IdP. Must be pre-configured in the IdP to prevent open redirects.
https://app.salesforce.com/sso/saml/acs: The IdP will POST the SAMLResponse only to this whitelisted URL.
Security
Replay Prevention
Each AuthnRequest has a unique ID. The assertion references it. The SP tracks used assertion IDs to prevent the same assertion being submitted twice.
Without this, an attacker who intercepts a valid assertion could resubmit it hours later to gain access. The InResponseTo + NotOnOrAfter fields prevent this.
Config
Metadata XML
Both SP and IdP publish a metadata XML file describing their endpoints, certificates, and capabilities. Exchange of metadata = the trust setup step.
Setting up SAML: download IdP metadata โ†’ upload to SP. Download SP metadata โ†’ upload to IdP. That's the entire trust relationship established.

Anatomy of a SAML Assertion

Here's a simplified (but real) SAML assertion. Every element has a purpose.

<!-- The SAML Response wrapper -->
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  ID="_unique-response-id"
  InResponseTo="_request-id-from-sp"    <!-- ties to AuthnRequest -->
  IssueInstant="2024-01-15T14:30:00Z"
  Status="Success">

  <saml:Issuer>https://idp.okta.com/saml</saml:Issuer>  <!-- who made this -->

  <!-- THE CORE ASSERTION -->
  <saml:Assertion
    ID="_unique-assertion-id"
    IssueInstant="2024-01-15T14:30:00Z">

    <!-- WHO this assertion is about -->
    <saml:Subject>
      <saml:NameID Format="emailAddress">
        alice@company.com
      </saml:NameID>
      <saml:SubjectConfirmation
        Recipient="https://app.salesforce.com/acs"  <!-- must match ACS URL -->
        NotOnOrAfter="2024-01-15T14:35:00Z"/>  <!-- expires in 5 min -->
    </saml:Subject>

    <!-- WHEN it's valid -->
    <saml:Conditions
      NotBefore="2024-01-15T14:29:55Z"
      NotOnOrAfter="2024-01-15T14:35:00Z">
      <saml:AudienceRestriction>
        https://app.salesforce.com  <!-- only valid for this SP -->
      </saml:AudienceRestriction>
    </saml:Conditions>

    <!-- HOW they authenticated -->
    <saml:AuthnStatement AuthnInstant="2024-01-15T14:30:00Z">
      <saml:AuthnContext>
        PasswordProtectedTransport  <!-- login method -->
      </saml:AuthnContext>
    </saml:AuthnStatement>

    <!-- USER ATTRIBUTES -->
    <saml:AttributeStatement>
      <saml:Attribute Name="email">
        <saml:AttributeValue>alice@company.com</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute Name="groups">
        <saml:AttributeValue>admins</saml:AttributeValue>
        <saml:AttributeValue>engineering</saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>

    <!-- DIGITAL SIGNATURE (IdP's private key) -->
    <ds:Signature>...</ds:Signature>

  </saml:Assertion>
</samlp:Response>
โš ๏ธ In real life this XML is Base64-encoded and often deflated (compressed). You never see raw XML in the browser. Use tools like samltool.com or browser extensions like "SAML Tracer" to decode and inspect it.

SP-Initiated vs IdP-Initiated

SP-Initiated โ† Most Common
User starts at the SP. SP sends them to IdP.
User โ†’ SP
SP โ†’ (redirect) โ†’ IdP
IdP โ†’ (POST) โ†’ SP
SP โ†’ User โœ“
Example: You go to slack.com, click "Sign in with SSO"
IdP-Initiated
User starts at the IdP dashboard. No AuthnRequest.
User โ†’ IdP portal
User clicks app icon
IdP โ†’ (POST) โ†’ SP
SP โ†’ User โœ“
Example: Okta dashboard โ†’ click "Salesforce" tile
๐Ÿ”ด Security note: IdP-initiated flow is considered less secure because there's no InResponseTo to match, the SP can't verify it triggered the request. This opens potential for unsolicited assertion attacks. Disable IdP-initiated if you don't need it.

SAML Bindings

A binding is how SAML messages are transported. Think of it as the envelope type for the XML letter.

HTTP-POST
Most Common
SAML response sent via HTML form auto-POST. Used for IdPโ†’SP (the assertion delivery). Data is in form body, not visible in browser history.
HTTP-Redirect
Common
SAML request sent via URL query param (Base64 + deflate). Used for SPโ†’IdP (the AuthnRequest). Size-limited by URL length.
HTTP-Artifact
Rare
A short artifact (reference) is sent via redirect, then SP fetches the real assertion directly from IdP. Avoids large URLs but requires back-channel.
SOAP
Legacy
Server-to-server XML/SOAP. Used for older back-channel communications. Rarely seen in modern setups.

SAML vs OAuth 2.0 vs OIDC

People confuse these constantly. Here's the clear breakdown:

Dimension SAML 2.0 OAuth 2.0 OIDC
Primary purpose Authentication + SSO Authorization (API access) Authentication (on top of OAuth)
Format XML JSON / Tokens JWT (JSON)
Era 2005 โ€” Enterprise 2012 โ€” Web/API 2014 โ€” Modern Web
Typical use Corporate SSO, legacy apps "Login with Google" API access Consumer apps, SPAs, mobile
Mobile friendly โœ— (browser-centric) โœ“ โœ“
Carries user attributes โœ“ Rich โœ— (not its job) ~ (ID Token)
Complexity High (XML, certs) Medium Medium-Low
Where you'll see it Salesforce, Workday, enterprise apps API integrations, social login Auth0, Google Sign-In, modern apps
๐ŸŽฏ Quick Distinction

OAuth: "App X wants to read your Google Calendar on your behalf. Allow?" โ€” It's about delegating API access.

OIDC: "Sign in with Google" โ€” It's about knowing who you are using OAuth infrastructure.

SAML: "Your company uses Okta. Log into Salesforce using your Okta account" โ€” It's about enterprise SSO across applications.

Common Pitfalls & Security Gotchas

๐Ÿ”ด
Clock Skew โ€” SAML assertions have strict timestamps. If the SP and IdP clocks differ by more than a few minutes, valid assertions will be rejected. Always sync clocks via NTP.
๐Ÿ”ด
Signature Not Validated โ€” Some misconfigured SPs don't enforce signature validation. An attacker can forge arbitrary assertions. Always require signed assertions AND responses.
โš ๏ธ
XML Signature Wrapping (XSW) โ€” A sophisticated attack where an attacker inserts a second unsigned assertion and tricks the SP into validating the wrong element. Use well-tested SAML libraries, never roll your own XML parsing.
โš ๏ธ
Audience Not Checked โ€” If SP doesn't verify that the assertion's Audience matches its own Entity ID, an assertion meant for App A could be used to log into App B.
โ„น๏ธ
Certificate Rotation โ€” When the IdP rotates its signing certificate, all SPs must update their trusted cert. Forgetting this causes sudden SSO outages for every connected app simultaneously.
โœ…
Best Practice โ€” Encrypt the assertion (not just sign it). Signing proves integrity; encryption ensures the IdP's message can only be read by the intended SP. Use AES-256 for encryption.

Quick Quiz