Announcing CIMD support for MCP Client registration
Learn more

SAML SSO in B2B SaaS: The complete guide for developers and enterprise buyers

TL;DR

  • SAML lets users sign in using their company’s identity provider (Okta, Entra ID, Ping, Google Workspace) instead of creating new passwords for your SaaS app.
  • The IdP authenticates the user and sends your application a signed SAML response, which your app trusts to create a session.
  • Enterprises prefer SAML because it centralizes MFA, access policies, and offboarding, all handled in one place.
  • For B2B SaaS, SAML SSO is a must-have. Many mid-market and enterprise deals block immediately if SSO isn’t supported.
  • Implementing SAML across multiple IdPs is complex (metadata formats, certificates, tenant mappings, assertion differences), and platforms like Scalekit remove this engineering overhead with unified SDKs and automated IdP integrations.

The role of SAML SSO in B2B SaaS architecture

Enterprise companies run dozens, often hundreds, of internal and external applications: CRMs, ERPs, HR systems, data tools, and SaaS platforms that their teams use every day. As usage scales, managing identity across this entire stack becomes unmanageable. To solve this, IT teams standardize on authentication via identity providers (IdPs) such as Okta, Microsoft Entra ID, Ping, or Google Workspace. These systems centralize login, MFA, password resets, and access policies in one place.

For SaaS vendors moving upmarket, this centralization directly shapes customer expectations. During early security reviews, enterprises assume your product will integrate with their existing IdP without requiring separate credentials or manual onboarding. This is where SAML becomes essential. It allows the customer’s IdP to authenticate the user and securely pass identity information to your application, enabling seamless sign-in without storing passwords.

For customers, this provides predictable governance and consistent MFA enforcement. For B2B SaaS teams, a lack of SAML support slows proofs of concept, complicates onboarding, and often becomes a blocker during enterprise procurement.

This guide builds the foundation you need to support enterprise SSO correctly. It explains how SAML differs from SSO, how IdPs and Service Providers (SPs) communicate, and how the complete flow works in practice. From core concepts to attributes, metadata, assertions, and ACS endpoints, you’ll have the background needed to follow the hands-on Okta example later and to design multi-tenant SSO integrations that scale.

SAML vs SSO: What SAML actually is

Many teams assume SAML is SSO, but they solve different problems.

SAML is a protocol. SSO is an experience.

  • SAML defines how identity is securely exchanged between an IdP (e.g., Okta) and an SP (e.g., your SaaS app).
  • SSO defines what the user experiences: sign in once → access everything without logging in again.
  • SAML can power SSO, but SSO can also be implemented using OIDC, Kerberos, or other protocols.

Here’s the relationship in one table:

Concept
What It Is
Purpose
Connection
SAML
XML-based protocol for secure identity exchange between IdP ↔ SP
Verify user identity + share attributes
Often used to deliver SSO
SSO
User signs in once and accesses multiple apps
Reduce friction, unify access
Can run on SAML, OIDC, Kerberos

This understanding prevents common mistakes, like treating SAML as a login method rather than what it actually is, a federation protocol used by IdPs and SPs to establish trust and exchange identity assertions.

SAML vs OIDC: How they differ in modern B2B SaaS

As soon as teams understand that SAML is a protocol and SSO is an experience, the next question becomes: Which standard should a SaaS product support, SAML, OIDC, or both?

Although both enable Single Sign-On, they were built for different environments and technical requirements. Understanding these distinctions helps product and engineering teams make the right decision for their architecture and customer base.

When SAML Is typically used

SAML is the prevailing choice in enterprise workforce identity, especially when integrating with providers such as Okta, Ping Identity, and Microsoft Entra ID. It fits well when:

  • Customers already rely on established SSO infrastructure.
  • Governance and compliance requirements mandate centralized authentication.
  • MFA, conditional access, and group-based policies are enforced at the IdP.
  • Onboarding must fit existing enterprise identity workflows.

This is why most B2B SaaS products selling to mid-market and enterprise buyers are expected to support SAML out of the box.

When OIDC is typically used

OIDC (OpenID Connect) is more common in modern web and mobile applications, developer tools, and consumer-facing products. It is preferred when:

  • Mobile-first authentication is required.
  • Applications depend on the lightweight JSON Web Token (JWT) format.
  • APIs and microservices need stateless access tokens.
  • Developer-friendly, REST-based flows are important.
  • Integrations with modern IdPs (Auth0, AWS Cognito, Google Identity) are needed.

OIDC is generally simpler to configure and integrates naturally with modern architectures.

High-Level comparison of SAML and OIDC

Category
SAML
OIDC
Standard Type
XML-based identity protocol
JSON/REST identity layer on OAuth 2.0
Primary Use Case
Enterprise workforce SSO
Modern SaaS, mobile, APIs
Token Format
XML Assertions
JWT (JSON Web Token)
Transport
Browser redirects + POST
REST APIs + redirects
MFA & Governance
Enforced by IdP policies
Enforced by IdP/Authorization Server policies
Mobile Support
Limited
Excellent
Adoption Patterns
Enterprise IT, legacy systems
Modern cloud-native/apps
Configuration Complexity
Higher
Lower

How to decide which standard to support

The decision for SaaS vendors often aligns with customer profile and technical architecture:

  • Enterprise-facing SaaS → SAML is expected during onboarding.
  • Developer-centric or mobile-first products → OIDC is more natural.
  • SaaS platforms aiming to serve both segments → Support both SAML and OIDC.

Most mid-market and enterprise SaaS companies eventually implement both to avoid friction during customer integrations.

SAML SSO architecture: Identity Provider (IdP) and Service Provider (SP)

SAML operates on a simple but powerful architectural model built around two systems: the Identity Provider (IdP) and the Service Provider (SP). Every SAML authentication flow depends on the trust established between these two components through metadata, certificates, and signatures.

IdP and SP responsibilities at a glance

Component
What It Does
Why It Matters in B2B SaaS
Identity Provider (IdP)
Authenticates users, enforces MFA and access policies, issues signed SAML Assertions
Centralized identity governance and consistent security controls
Service Provider (SP)
Validates assertions, extracts user attributes, and creates local sessions
Eliminates password storage and enables seamless SSO integrations

How trust is established between IdP and SP

In web-based SSO flows, the IdP authenticates the user and issues a signed SAML Assertion. The SP validates this assertion using the IdP’s certificate, processes user attributes, and establishes an authenticated session.

This trust relationship is established through metadata exchange, where each side publishes its configuration:

What IdP Metadata typically contains

  • Identity Provider EntityID
  • SSO (Single Sign-On) endpoints
  • SLO (Single Logout) endpoints
  • Certificates for verifying signatures
  • Supported bindings (Redirect, POST)

What SP Metadata typically contains

  • Service Provider EntityID
  • ACS (Assertion Consumer Service) URL
  • Signing/encryption certificates
  • Supported NameID formats

Both systems rely on this metadata to securely route requests, validate signatures, and ensure messages originate from trusted sources.

Why this model works for enterprise SaaS

Enterprise organizations standardize authentication across multiple applications. When your SaaS product integrates with a customer’s IdP:

  • Authentication moves out of your application and into their security perimeter.
  • The customer enforces MFA, conditional access, and password policies in one place.
  • Your application receives a signed assertion without storing user credentials.
  • Onboarding and offboarding become easier through centralized identity governance.

This pattern aligns directly with enterprise expectations, reduces support burden, and enables a scalable, secure authentication model for multi-tenant SaaS platforms.

How SAML Fits Into B2B SaaS Multi-Tenancy and User Access Models

B2B SaaS applications almost always operate in a multi-tenant environment, where each platform serves multiple customer organizations with their own users, roles, and policies. SAML fits naturally into this model because every tenant can connect its own Identity Provider (IdP), such as Okta or Entra ID.

Mapping SAML logins to tenants

When a user signs in through SAML, the Service Provider (SP) determines:

  • Which tenant the user belongs to
  • What role or access level should they receive
  • What resources can they access

SaaS apps typically map SAML attributes such as:

  • Email domain (e.g., @acme.com)
  • Tenant identifier passed by the IdP
  • Groups/Roles defined in the enterprise directory

This lets enterprises manage identities and roles centrally while the SaaS product keeps authorization lightweight and consistent.

Advanced Multi-Tenant patterns

More mature B2B setups extend this further by supporting:

  • Multiple IdPs per tenant (common for subsidiaries or regional divisions)
  • Per-tenant metadata and certificate management
  • Dynamic attribute mappings for roles and groups
  • Just-in-Time (JIT) provisioning for auto-creating users at first login
  • SCIM for automated lifecycle management (provisioning, deactivation, role updates)

These patterns allow SaaS teams to deliver seamless onboarding, strong governance alignment, and scalable multi-tenant authentication, without managing separate passwords for every user.

The core components that power SAML SSO

SAML (Security Assertion Markup Language), finalized in 2005 by OASIS, is an XML-based standard that allows an Identity Provider (IdP) to authenticate a user and securely pass identity information to a Service Provider (SP). It enables Single Sign-On without shared passwords and lets enterprises centralize authentication while SaaS applications focus on authorization and session management.

At a high level, SAML relies on a trust relationship between the IdP and the SP. The IdP authenticates the user and issues a signed SAML assertion; the SP validates this assertion using metadata (certificates, endpoints, identifiers) exchanged between the two parties. This metadata-driven trust ensures secure, interoperable communication across different providers and applications.

Before implementing SAML, let’s review its components and concepts:

  • Authentication and Authorization
  • Identity Provider and Service Provider
  • SAML Request and SAML Response
  • SAML Metadata
  • SAML Assertions
  • SAML Attributes
  • SAML Bindings
  • SAML Protocols
  • SAML Profiles
  • Relay State
  • XML Signature

Authentication and authorization

Authentication defines how a user is identified and validated using credentials, typically a username and password, during the sign-in process. Authorization, in contrast, defines which resources users have access to. The SAML protocol facilitates this data exchange between an Identity Provider (IdP) and a Service Provider (SP).

Identity Provider (IdP) and Service Provider (SP)

This section uses the same IdP and SP roles described above but shifts focus to their responsibilities within the SAML flow. The SP is the application the user wants to access, while the IdP manages user identities and attributes such as firstName, nickname, profileURL, and more. 

A typical SAML authentication workflow involves the following steps:

  1. The end user (browser) requests the SP to access a protected resource
  2. The SP initiates the login by sending a SAML request to the IdP
  3. The IdP authenticates the user, generates the SAML assertion, and sends it back to the SP
  4. The SP validates the SAML assertion and ascertains the user's identity
  5. Based on the identity, the SP grants the user access to the resource.
  6. To plan redirects and user journeys, take a look at IDP-initiated vs. SP-initiated SSO.

SP accomplishes this using the following three components:

  • IdP Certificate: The certificate is stored in the SP application and used to verify the SAML Response signature.
  • Assertion Consumer Service (ACS) URL: The endpoint provided by the SP to which SAML responses are sent and processed. The SP shares this URL with the IdP.
  • IdP Sign-in URL: The endpoint on the IdP side where SAML requests are submitted. The SP obtains this information from the IdP metadata.

These elements ensure that the SP can securely receive and process SAML assertions, enabling seamless user authentication.

SAML request and response

A SAML Request is generated by the SP to ask the IdP to authenticate the user.

A SAML Response is generated by the IdP and contains the signed SAML Assertion.

Example of a SAML Request 

<AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="id-UiAc6N6yZMzhEsxo4" Version="2.0" IssueInstant="2024-06-13T09:31:01Z" Destination="https://dev-17138448.okta.com/app/dev-17138448_samplesaasapp2_1/exkhpfq8loNJsfLlm5d7/sso/saml" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="http://localhost:8443/saml/acs/" > <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"> http://localhost:8443/saml/metadata/ </saml:Issuer> <NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" AllowCreate="false" /> </AuthnRequest>

To view the complete request, refer to the sample_request.xml file.

Example of SAML Response from IdP for authentication with additional attributes.

<saml2:Attribute Name="lastname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" > <saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string" > Turing </saml2:AttributeValue> </saml2:Attribute>

To view the complete request, refer to the sample_response.xml file.

SAML assertions

SAML Assertions are XML documents issued by the IdP that contain statements about a user. The SP uses these assertions to grant or deny access to resources. Assertions consist of:

  • Authentication Statements: Confirm that the user has been authenticated.
  • Attribute Statements: Provide additional information about the user (e.g., email, roles).
  • Authorization Decision Statements: Indicate the user's access rights.

Assertions serve as the core of SAML authentication, enabling the secure transfer of user identity and attributes between IdP and SP, facilitating Single Sign-On.

SAML attributes

SAML Attributes carry additional user information within assertions. Common attributes include

  • NameID: A unique identifier for the user.
  • Email: The user's email address.
  • Roles: User roles or group memberships.

Attributes enable SPs to enforce fine-grained access control and customize user experiences based on identity information.

XML signature (DSig)

XML Signature ensures the integrity and authenticity of SAML messages. It uses digital signatures to verify that the message has not been altered and comes from a trusted source.

SAML metadata

SAML Metadata provides configuration details about the IdP and SP. The IdP metadata includes entity ID, Single Sign On (SSO), and Single Logout (SLO) endpoints. For the SP, it includes details like the Assertion Consumer Service (ACS) endpoints, entity ID, and certificates. It facilitates interoperability by allowing entities to share and understand each other's configuration.

Example SAML Metadata of IdP.

<md:NameIDFormat> urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress </md:NameIDFormat> <md:NameIDFormat> urn:oasis:names:tc:SAML:2.0:nameid-format:persistent </md:NameIDFormat> <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://dev-17138448.okta.com/app/dev-17138448_samplesaasapp2_1/exkhpfq8loNJsfLlm5d7/sso/saml" /> <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://dev-17138448.okta.com/app/dev-17138448_samplesaasapp2_1/exkhpfq8loNJsfLlm5d7/sso/saml" /> </md:IDPSSODescriptor>

To view the complete request, refer to the sample_idp-metada.xml file.

SAML bindings

Bindings specify how SAML messages are transported between IdP and SP. Common bindings include

  • HTTP Redirect: Transports SAML messages via URL query parameters. Used for sending authentication requests.
  • HTTP POST: Encodes SAML messages within an HTML form and uses the POST method. It is commonly used to send SAML responses.

In the above example, IdP SAML Metadata, HTTP POST, and Redirect bindings are defined for specific URLs for SingleSignOnService and SingleLogoutService.

SAML profiles

Profiles define specific use cases for SAML, detailing how assertions, protocols, and bindings should be used together. Common profiles include:

  • Web Browser SSO Profile: Facilitates SSO for web applications.
  • Single Logout Profile: Allows users to log out from all connected services simultaneously.

Relay state

Relay State is an optional parameter that maintains state information between the IdP and SP during authentication. It typically contains a URL or a reference that the SP will redirect to after authentication. This helps users return to the exact page they came from or continue a flow seamlessly after SSO.

Most enterprise IdPs also impose strict size limits on RelayState values (commonly 80–1024 bytes), so it should always be an opaque, short identifier rather than a long encoded URL or user-specific payload. RelayState must also never contain sensitive data, since it is passed through the browser and can be tampered with. Instead, the SP should store any needed information server-side and reference it via a short token in RelayState.

Building a SAML SSO flow with Okta

SAML 2.0, standardized by OASIS in 2005, remains one of the most widely used authentication protocols in enterprise environments. Many organizations run identity systems like Okta, Microsoft Entra ID, OneLogin, or PingFederate, making SAML SSO support essential for B2B SaaS applications.

In this section, we walk through integrating Okta as an Identity Provider (IdP) and building a working SAML SSO flow using a simple sample application. The hands-on example mirrors what typically happens during enterprise onboarding: the customer configures your application inside their IdP, and your application consumes the SAML assertion to authenticate users.

Use case

We’ll build a simple web application with two pages: login and profile. The login page will feature a "Login with SAML SSO" button. The profile page will display user details such as email, name, and photo, as well as the SAML Response Attributes and the SAML Response itself.

We will use Okta's SAML 2.0 Integration to configure our application for Single Sign-On. In this setup, our application will require a login, and Okta will serve as the IdP, providing authentication details via SAML SSO and our application acts as the Service Provider (SP).

Pre-requisites

Building a SAML SSO Flow using Okta requires the following prerequisites:

  • A web application with a login page – We’ve built one using Python and Flask; you can use the same tools to build one. You can find our application in this GitHub repository.
  • Okta developer account: Sign up for an Okta developer account if you don’t have one.

Two configurations are needed to build this flow: 

  • Configure Okta as IdP by creating a new application with SAML 2.0 SSO
  • Configure the application with SAML support. 

The following sections will describe them.

SAML flow - Configuring Okta as IdP

To integrate SSO with Okta, we must first create a new application from the Okta Dashboard.

This setup establishes the trust relationship between your application (SP) and Okta (IdP), which is required before SAML requests and responses can be exchanged.

1. Log in to your Okta Developer account, go to Applications from the Okta sidebar, click Create New App, select SAML 2.0, and click Next. 

2. On the next screen, provide a name for the application.In this case, we’re adding a Sample SaaS App. You can add a logo to customize the login logo. This step creates the IdP-side representation of your SaaS product.

3. In the Configure SAML tab, configure the following:

  • Single Sign-On URL (ACS URL):
    Enter the Assertion Consumer Service (ACS) URL of your application.
    This is the endpoint where the IdP will send the SAML Response (Assertion) using HTTP POST after authentication.

In our sample app, the ACS URL is: /saml/acs/

  • NameID Format:
    Set this to Persistent, which defines how the IdP identifies the user in the SAML assertion.

These two fields are the core of the trust configuration. The IdP must know where to send the response and in what format user identifiers should be passed.

4. On the same page, navigate to the Attributes Statements section to configure the following attributes and group attribute statements to be sent when the authentication is successful. These attributes will later map to user profiles and permissions on the SP side.

Note: To add more attributes to a User profile, you can go to Directory in the Okta sidebar- > click on Profile Editor ->  All User -> Click on Okta User -> Click on Add Attributes.

5. In the Feedback tab, check the App type checkbox “This is an internal app that we have created” since we’re testing it and not releasing it publicly. Click Finish. 

After finishing the application creation, it should show you the Metadata URL, which will have all the details of IdP metadata that we will use in the next step, so download the content from 

that link and save that XML as idp-metadata.xml.

This metadata file is essential because it contains certificates and endpoints that your SP uses to validate signatures and route SAML traffic.

Once the application is created, assign users or groups who will use it. In this case, we'll use the same user who created the Okta account.

6. Go to Sample SaaS App -> Assignment -> Assign -> Assign to People -> Select your user -> click on the Assign button next to your user -> Save and go Back. -> Done.

7. Now, do the same for groups. Go to Sample SaaS App -> Assignment -> Assign -> Assign to Groups > click on the Assign button next to your Groups -> Done.

At this point, we have created an application in Okta for SSO configuration using SAML 2.0. It is an internal application, but we can make it public later as required. When public, it can be made available like all other applications in Okta integrations, such as Google Workspace and Salesforce. 

This completes the IdP-side setup. The next step is configuring your application (the SP) to accept and validate SAML responses from Okta.

Configuring the sample application with SAML SSO


As mentioned, we have built a simple web application using Python and Flask. The app uses the PySAML2 library, which provides the core SAML 2.0 functionality required for acting as a Service Provider (SP). This section demonstrates how your SP consumes the metadata configured in Okta and completes the authentication flow.

Below is the repository structure

├── conf ├── LICENSE ├── main.py ├── README.md ├── requirements.txt └── templates

Move the idp-metadata.xml file we downloaded from Okta into the conf directory. After moving the file, the folder should look like this. SP can now automatically fetch IdP metadata from a remote URL and gracefully fall back. This is required for production setups where enterprises rotate certificates or publish dynamic metadata.

conf ├── idp-metadata.xml └── sp_conf.py 0 directories, 2 files

Now, we configure the sp_conf.py file, which contains all SAML settings for the SP application. This is where the SP declares its ACS endpoint, metadata URL, certificates, and expected bindings.

import os from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST from saml2.saml import NAMEID_FORMAT_PERSISTENT # Base URL for the SP (can be overridden via env for different deploys) BASE_URL = os.environ.get("BASE_URL", "http://localhost:8433") # Resolve default local metadata path (conf/idp-metadata.xml) BASE_DIR = os.path.dirname(__file__) DEFAULT_LOCAL_METADATA = os.path.normpath( os.path.join(BASE_DIR, "idp-metadata.xml") ) # Metadata configuration - prefer remote URL if provided # Default to env-provided metadata URL, or fall back to a known Okta metadata # fetch. Override by setting `IDP_METADATA_URL` or `METADATA_URL`. METADATA_URL = ( os.environ.get("IDP_METADATA_URL") or os.environ.get("METADATA_URL") ) # Allow overriding local metadata path via env METADATA_LOCAL = os.environ.get( "IDP_METADATA_LOCAL", DEFAULT_LOCAL_METADATA ) # If the local file exists, prefer it. # Set FORCE_REMOTE=1 to force using METADATA_URL. FORCE_REMOTE = os.environ.get("FORCE_REMOTE", "") in ("1", "true", "True") if (not FORCE_REMOTE) and os.path.exists(METADATA_LOCAL): metadata_cfg = {"local": [METADATA_LOCAL]} elif METADATA_URL: metadata_cfg = {"remote": [{"url": METADATA_URL}]} else: metadata_cfg = {"local": [METADATA_LOCAL]} CONFIG = { "entityid": BASE_URL + "/saml/metadata/", "service": { "sp": { "name": "SaaS Sample App", "name_id_policy_format": NAMEID_FORMAT_PERSISTENT, "endpoints": { "assertion_consumer_service": [ (BASE_URL + "/saml/acs/", BINDING_HTTP_POST), ], }, "allow_unsolicited": True, "authn_requests_signed": False, "want_assertions_signed": True, "want_response_signed": True, }, }, # Metadata source - either remote URL(s) or local file(s) "metadata": metadata_cfg, }

Security Note on ‘allow_unsolicited’ = True

Since the sample sets allow_unsolicited = True, the SP accepts IdP-initiated SSO flows (responses without a prior AuthnRequest). This is common in enterprise setups but requires careful handling:

  • Enforce strict Audience and Recipient checks.
  • Validate RelayState to ensure it hasn’t been tampered with.
  • Make sure the ACS endpoint is always served over TLS 1.2+ in production.
  • Do not expose the ACS endpoint over plain HTTP outside local development.

If your application does not need IdP-initiated SSO, set: 'allow_unsolicited': False

This restricts the SP to only SP-initiated authentication flows.
In this configuration:

  • metadata supports both local and remote IdP metadata.
    If a remote metadata URL is provided (IDP_METADATA_URL or METADATA_URL), the application can automatically fetch and cache it. Otherwise, it falls back to the local idp-metadata.xml file inside the conf folder. This makes the setup resilient to IdP certificate rotations and reduces the need for manual file updates.
  • assertion_consumer_service is the endpoint where the IdP posts the SAML Response (/saml/metadata/).
  • entityid defines your SP metadata URL (/saml/metadata).

Note: If you change your port, domain, or metadata file location, update both your SP configuration and your Okta application settings accordingly.

The automated metadata handling ensures your application remains stable even when IdPs rotate certificates, issue new metadata, or require dynamic updates.

Automated metadata handling

In addition to supporting local metadata files, the sample application includes optional automatic metadata retrieval. If an environment variable such as IDP_METADATA_URL is provided, the app will fetch and cache the IdP’s metadata at startup. This makes the integration more resilient to IdP certificate rotations and configuration changes, removing the need to manually download or replace idp-metadata.xml. If a local file exists, it is used by default unless FORCE_REMOTE is set.

try: if METADATA_URL and not os.path.exists(METADATA_LOCAL): try: # Use stdlib to avoid external dependency on `requests` from urllib.request import urlopen, Request from urllib.error import URLError, HTTPError print(f"Fetching metadata from: {METADATA_URL}") req = Request( METADATA_URL, headers={"User-Agent": "metadata-fetcher/1.0"} ) with urlopen(req, timeout=20) as resp: content = resp.read().decode("utf-8") if ( "


Next, in main.py, we configure a helper function that loads the SAML configuration and returns a ready-to-use PySAML2 client:

def saml_client_for(config): """ Create a SAML client using the provided configuration. Args: config (str): Path to the SAML configuration file. Returns: Saml2Client: A SAML client instance. """ conf = Saml2Config() conf.load(config) return Saml2Client(conf)

When a user visits the login page and clicks Login with SAML SSO, the /login route generates an authentication request and redirects the user to the Okta-hosted login page:

@app.route('/login') def login(): """ Initiates the SAML authentication process by creating an authentication request and redirecting the user to the IdP's login page. Returns: If the redirect URL is available, the function redirects the user to the IdP's login page. If the redirect URL is not available, the function returns an error message with status code 500. """ client = saml_client_for(CONFIG) # Create the SAML authentication request session_id, result = client.prepare_for_authenticate() # Redirect the user to the IdP's login page redirect_url = None for key, value in result["headers"]: if key == "Location": redirect_url = value break if redirect_url: return redirect(redirect_url) else: return "Error: Couldn't redirect to IdP for login.", 500

This request is what initiates the SAML authentication cycle and sends the user to the IdP.

Once the SAML Authentication Request is sent to the IdP, a SAML response is returned to the Assertion Consumer Service. 

Below is our ACS service, which handles the SAML response coming from the IdP. In our case, the ACS service also sets the session details if the auth response is successful and redirects to the dynamic profile page.

@app.route('/saml/acs/', methods=['POST']) def acs(): """ Process the SAML Assertion Consumer Service (ACS) request. This function handles the SAML response received from the Identity Provider (IdP) after the user has been authenticated. It extracts the user's attributes from the SAML response, sets the user session, and redirects the user to the 'hello' endpoint. Returns: A redirect response to the 'hello' endpoint if the SAML response is successfully processed. An error message with status code 500 if there is an exception during processing. """ client = saml_client_for(CONFIG) try: # Parse the SAML Response saml_response = request.form.get('SAMLResponse') authn_response = client.parse_authn_request_response( saml_response, entity.BINDING_HTTP_POST ) if authn_response is None: debug_log("parse_authn_request_response returned None") session.clear() return "Invalid SAML response", 400 debug_log( f"authn_response.status_ok(): {authn_response.status_ok()}" ) # Extract the user's NameID and attributes name_id = authn_response.assertion.subject.name_id.text debug_log(f"name_id: {name_id}") # Access attributes directly from the assertion attributes = {} for statement in getattr( authn_response.assertion, "attribute_statement", [] ): for attribute in getattr(statement, "attribute", []): try: # Assuming single-value attributes attributes[ attribute.name ] = attribute.attribute_value[0].text except Exception: # Skip malformed attributes continue # Convert authn_response object to string authn_response_string = str(authn_response) # Store authn_response string in session session["authn_response_string"] = authn_response_string # Set the user session session["name_id"] = name_id session["firstname"] = attributes.get("firstname") session["lastname"] = attributes.get("lastname") session["email"] = attributes.get("email") session["profileUrl"] = attributes.get("profileUrl") session["attributes"] = attributes session["is_authenticated"] = True return redirect(url_for("hello")) except SignatureError: debug_log("Signature validation failed.") session.clear() return "Invalid or tampered signature", 403 except AudienceError: debug_log("Audience mismatch. Check SP entityID.") session.clear() return "Audience restriction failed", 403 except ResponseLifetimeError: debug_log("SAML assertion expired or not yet valid.") session.clear() return "Expired assertion", 403 except IncorrectlySigned: debug_log("Incorrectly signed response.") session.clear() return "Invalid signature format", 403 except Exception as e: # Log the exception for debugging debug_log(f"SAML ACS Error: {e}") session.clear() return f"EXCEPTION {e}", 500

Now that authentication is complete, we add a simple logout endpoint:

@app.route('/logout') def logout(): """ Clear the current session. """ session.clear() return redirect(url_for('hello'))


This clears the local session on your SP. For full SAML Single Logout (SLO), the IdP-initiated flow can also be added later.

Finally, the home page renders the user profile after login. This endpoint displays:

@app.route("/") def hello(): """ Displays a welcome message and user information if the user is authenticated, otherwise displays a login link. Returns: HTML content and the HTTP status code. """ # Check if user is authenticated is_authenticated = session.get("is_authenticated", False) if is_authenticated: name_id = escape(session.get("name_id", "")) first_name = escape(session.get("firstname", "")) last_name = escape(session.get("last name", "")) email = escape(session.get("email", "")) profileUrl = escape(session.get("profileUrl", "")) authn_response_string = session.get( "authn_response_string", "" ) attributes = session.get("attributes", "{}") return ( render_template( "template.html", is_authenticated=is_authenticated, name_id=name_id, first_name=first_name, last_name=last_name, email=email, authn_response_string=authn_response_string, profileUrl=profileUrl, attributes=attributes, ), 200, ) else: metadata_url = BASE_URL + "/saml/metadata" return ( render_template( "template.html", is_authenticated=is_authenticated, metadata_url=metadata_url, ), 200, )

The complete main.py file can be found here.

The application’s UI is an HTML page with a “Login with SAML SSO” button that lets users log in to the application via Okta.

Execution 

To execute this app, start the Flask server:

python main.py

With this, your application will be available at https://localhost:8443/.

You can log in to the app by clicking the Login with SAML SSO button; it should redirect you to the Okta login page.

Use the credentials of the user who was assigned to the application.

After authentication, Okta will send a SAML Response to the SP application's ACS endpoint. If the response is valid, the ACS endpoint will create the session and redirect you back to the home page, where the logged-in user's details will appear.

To understand what is happening behind the scenes, tools like SAML-tracer (available for Firefox and Chrome) allow you to inspect the raw SAML Request and Response exchanged during login.

Below is the SAML Request sent to IdP:

Below is the SAML response from IdP:

After a successful login, you can see the full name, first name, last name, email ID, and user profile picture. 


That’s how you configure Okta SSO using SAML for a web application. The same approach can be applied to any other site or SaaS application that requires SAML-based authentication.

Error handling and debugging SAML SSO

SAML integrations often fail not because the protocol is complex, but because different IdPs send slightly different metadata, certificates, and attributes. When onboarding enterprise customers, clear error handling helps diagnose issues quickly and avoid long support cycles.

Common issues

Most SAML failures trace back to a few categories:

  • Signature validation errors (wrong or rotated certificates)
  • Incorrect ACS URL or entityID
  • Missing required attributes (email, NameID)
  • NameID format mismatches
  • Expired or not-yet-valid assertions (clock drift)

These tend to surface when integrating a new IdP or during certificate rotation.

Debugging SAML issues in development and production

SAML integrations can fail for subtle reasons, such as incorrect metadata, mismatched certificates, expired assertions, or binding errors. A reliable debugging workflow helps teams quickly identify where failures occur and why authentication breaks.

Use SAML-tracer to inspect SAML traffic.

Browser extensions like SAML-Tracer (Chrome/Firefox) let you capture and inspect:

  • AuthnRequests and SAMLResponses
  • Full XML assertions
  • Signature blocks and certificates
  • NameID values and formats
  • Binding type (POST vs Redirect)
  • RelayState and ACS URLs

This is often the fastest way to diagnose missing attributes, wrong URLs, or misconfigured IdP bindings

Enable verbose logging on the Service Provider.

Your application (the SP) should log signature validation, audience checks, assertion windows, and attributes whenever it processes a SAML response. Turning silent failures into explicit logs dramatically reduces debugging time.

app.post("/saml/acs", async (req, res) => { const samlResponse = req.body.SAMLResponse; // Decode and log raw XML const xml = Buffer.from(samlResponse, "base64").toString("utf8"); console.log("📥 Raw SAML Response XML:\n", xml); try { const { profile } = await samlStrategy.validatePostResponse(req.body); // Log important validation details console.log("🔐 Signature valid:", profile.signatureValid); console.log("👤 NameID:", profile.nameID); console.log("📩 Email attribute:", profile.email); console.log("🎯 Audience:", profile.audience); console.log("🔗 Recipient:", profile.recipient); // Assertion validity windows console.log("⏳ Assertion Time Window:", { notBefore: profile.conditions?.notBefore, notOnOrAfter: profile.conditions?.notOnOrAfter, }); // Clock skew check const now = new Date(); const notBefore = new Date(profile.conditions.notBefore); const notOnOrAfter = new Date(profile.conditions.notOnOrAfter); if (now < notBefore || now >= notOnOrAfter) { console.error( "❌ Assertion expired or not yet valid (possible clock drift)" ); } res.send("Login successful"); } catch (err) { console.error("❌ SAML validation failed:", err); res.status(401).send("SAML Error"); } });

This snippet demonstrates how to capture raw XML, log validation results, detect assertion time issues, and isolate signature errors.

Compare IdP and SP metadata carefully.

Most SAML configuration issues stem from subtle metadata mismatches. Always verify:

  • The ACS URL exactly matches what the IdP uses (no trailing-slash difference).
  • The EntityID is identical on both sides.
  • The IdP certificate matches what your SP expects.
  • Both sides use the same bindings (POST or Redirect).

Metadata drift is common after certificate rotation, IdP admin changes, or directory syncs.

Use SAML validators to inspect XML and signatures.

Online validators such as SAMLTool, OneLogin XML Validator, and samltool.com help check:

  • XML formatting
  • Signature correctness
  • Encryption blocks
  • Canonicalization
  • Schema validity

These tools are especially useful when working with custom or legacy IdPs that deviate from the SAML spec.

Verify time and clock synchronization.

SAML assertions rely heavily on strict time windows:

  • NotBefore: when the assertion becomes valid
  • NotOnOrAfter: when the assertion expires

Even a 5–10 second clock drift can cause intermittent authentication failures.

Ensure:

  • SP servers and IdP servers use NTP
  • Your SAML library’s time skew tolerance is configured (e.g., ±2 minutes)

This eliminates one of the most common production-only failures.

SAML best practices: Enterprise-Ready implementation checklist

The earlier example showed a development-friendly SAML integration. Production SSO for enterprise customers requires stronger guardrails around security, metadata reliability, and multi-tenant configuration. The checklist below captures the essentials.

1. Security hardening

  • Validate all SAML XML schemas to prevent XML signature wrapping and malformed responses.
  • Use canonicalized XML before verifying signatures to avoid signature manipulation.
  • Rely on secure, maintained XML libraries to avoid XXE and unsafe parsing issues.
  • Require signed responses and assertions; never accept unsigned SAML.
  • Use SHA-2 certificates; reject self-signed certificates for strong cryptographic assurance.
  • Encrypt assertions whenever possible to protect identity data in transit.

2. Certificate & Metadata management

  • Validate full certificate trust chains and revocation status to prevent spoofed certificates.
  • Automate metadata retrieval rather than hard-coding XML files required for certificate rotations.
  • Store metadata and certificates per tenant, since each customer brings its own IdP.

3. Multi-IdP & Multi-Tenant behavior

  • Support multiple IdPs per tenant when needed, as is common in enterprises with subsidiaries.
  • Allow flexible attribute mappings (email domain, tenant ID, groups, roles) to correctly route users.
  • Adopt JIT provisioning and SCIM to automate user lifecycle management across tenants.

4. Operational reliability

  • Enable structured logging across the SAML flow, signature status, timing checks, and attribute extraction.
  • Restrict XML parser capabilities (disable external entities, limit resources).
  • Use TLS 1.2+ for all SP endpoints to meet enterprise transport security standards.
  • Implement Single Logout (SLO) when using Okta or similar IdPs for consistent user session cleanup.

5. Monitoring & troubleshooting readiness

  • Track frequent failure points:
    expired assertions, audience mismatch, wrong certificate, missing attributes, signature failures, RelayState issues.
  • Use internal-only tools like SAML-Tracer to inspect request/response pairs for faster debugging.

Conclusion

Enterprise SaaS products must integrate with customer identity providers such as Okta and Microsoft Entra ID. Centralized authentication is now a baseline expectation for mid-market and enterprise buyers, and SAML SSO remains the most widely used standard for workforce applications. Supporting it ensures predictable onboarding, unified MFA enforcement, and lower security risk.

Implementing SAML manually is possible, but in production environments, you'll encounter metadata variations, certificate rotations, signature validation issues, and IdP-specific quirks. These edge cases often slow down engineering teams and introduce failure points across environments. A reliable approach requires handling assertions, ACS endpoints, and bindings securely and consistently.

Scalekit helps SaaS teams ship enterprise-grade authentication without maintaining custom SAML plumbing. Its SDKs and APIs simplify integrations across IdPs and also support OIDC and SCIM when your customers request them. If you want to accelerate enterprise onboarding or extend your SSO support, Scalekit’s Core Auth is a natural next step to explore.

FAQs

1. Is SAML still relevant if OIDC is newer?

Yes. SAML remains the dominant standard for enterprise workforce SSO because it integrates deeply with legacy infrastructure, HR systems, governance tools, and established IdPs like Okta and Entra ID. OIDC is growing, but does not replace SAML in many enterprise IT environments.

2. Do I need to support both SAML and OIDC in my SaaS product?

If you sell to mid-market or enterprise organizations, the answer is usually yes. Many customers use SAML internally, while some modern platforms use OIDC. Supporting both avoids deal blockers during integrations.

3. How does Scalekit help with SAML SSO?

Scalekit removes the complexity of manual SAML setup by handling metadata parsing, certificate management, assertion validation, ACS routing, IdP differences, and error handling. You integrate once using an SDK and get a consistent SAML experience across all IdPs.

4. Can Scalekit help with OIDC and future identity standards too?

Yes. Scalekit provides unified support for SAML, OIDC, SCIM, and other enterprise auth requirements. Teams avoid building multiple authentication pipelines and instead rely on a single API surface that supports current and emerging standards.

5. How do enterprises typically evaluate SAML SSO implementations?

They look for correct assertion validation, secure certificate handling, signed responses, configurable ACS endpoints, JIT/auto-provisioning behavior, IdP mapping options, and reliable error reporting. Passing these checks significantly reduces onboarding friction.

No items found.
On this page
Share this article

Acquire enterprise customers with zero upfront cost

Every feature unlocked. No hidden fees.
Start Free
$0
/ month
1 million Monthly Active Users
100 Monthly Active Organizations
1 SSO connection
1 SCIM connection
10K Connected Accounts
Unlimited Dev & Prod environments