SSO

SAML implementation in B2B SaaS apps: A Step-by-Step Guide for developers

Srinivas Karre
CONTENTS

Enterprise organizations today use multiple applications for various needs, such as project management, CRM, ERP, and HR. To streamline access management, IT admins use centralized identity providers (IdPs) such as Okta and Microsoft Entra ID, which benefit both users and administrators. 

Users don’t need to remember multiple passwords; they can use their company’s credentials to log in to different applications. An identity system like Okta simplifies the password reset process and allows administrators to enforce different policies, making user access management of multiple applications easier. 

This centralized approach also enhances the organization's security posture, as users don’t need to go through password fatigue of reusing passwords across applications and resetting passwords repeatedly. 

To attract enterprise organizations, B2B SaaS applications must facilitate authentication with these centralized identity providers, enabling seamless access. One of the standards facilitating this seamless access is SAML.

In this blog, we will examine SAML and its role in B2B SaaS apps and provide a step-by-step guide for developers to implement SAML using Okta.

Understanding SAML Components and Concepts

SAML (Security Assertion Markup Language),  finalized in 2005 by the OASIS foundation, is designed to promote seamless interoperability between B2B SaaS apps and Identity Providers (IdPs). It is an XML-based framework for authentication and authorization, allowing users to authenticate once and gain access across multiple 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 a set of credentials, usually 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)

In the context of web browser-based Single Sign-On (SSO), SAML defines two primary roles: the Service Provider (SP) and the Identity Provider (IdP). The SP is the entity that offers a service to the user, typically an application users are trying to log in to. The IdP manages user identities, including profile information and attributes like firstname, nickname, profile URL, 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 access to the resource for the user.
SAML IdP SP
Source: https://developer.fourth.com/sites/default/files/inline-images/saml-sp-first.png

SP accomplishes this using the following three components:

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

These elements ensure that the SP can securely receive and process SAML assertions, allowing users to authenticate seamlessly.

SAML Request and Response

When the SP initiates a request to IdP, it is called a SAML Request. When the IdP responds, it is called a  SAML Response. 

Example of a SAML Request initiated from a SP application:

<ns0:AuthnRequest xmlns:ns0="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:ns1="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/">
  <ns1:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
   http://localhost:8443/saml/metadata/</ns1:Issuer>
  <ns0:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
   AllowCreate="false" />
</ns0: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. It helps users return to the same page they left off, or be redirected to the home page. 

Building a SAML SSO Flow with Okta

SAML 2.0 was ratified as an OASIS Standard in March 2005, replacing SAML 1.1. Although introduced in 2005, SAML 2.0 is still widely adopted across many enterprises and SaaS applications. This creates a need to support SAML-based authentication from service and identity providers. One of the identity providers (IdPs) that supports SAML is Okta, allowing developers to configure their applications to use Okta SAML 2.0 SSO Integrations for secure and reliable authentication.

In this section, we’ll examine Okta SAML 2.0 Integration for SSO and build a SAML 2.0 Flow using a sample application.

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.

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.

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

Configure Okta as IdP

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.

Create SAML Integration

3. In the Configure SAML tab, do the following two things:

  • Add the Single Sign-on URL where your application sends the SAML assertion with an HTTP POST, also known as the SAML Assertion Consumer Service (ACS) URL. Sample SaaS App will configure ACS URL at /saml/acs/ and provide SP metadata at this endpoint /saml/metadata. 
  • Select the Name ID format to Persistent, which identifies the SAML processing rules and constraints for the assertion's subject statement.
Configure SAML Integration

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.

Configure SAML attributes

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. 

SAML Integration Feedback

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 

Sample SaaS App

Once the application is created, assign users or groups that will use this application. In this case, we'll use the same user we used to create 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.

Sample SaaS app assignment

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.

Sample SaaS App Assignments

At this point, we have created an application in Okta that will be used 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 available like all other applications in the Okta integrations, such as Google Workspace, Salesforce, etc. 

Configuring Sample Application with SAML SSO

As mentioned, we have built a simple web application using Python and Flask. We have also used PySAML2 library that handles SAML 2.0 functionality in Python applications as both SP and IdP.

Below is the repository structure

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

Move the idp-metadata.xml we downloaded earlier under the conf directory. It should look like this – 

conf
├── idp-metadata.xml
├── sp_conf.py

0 directories, 2 files

Now, we need to configure the sp_conf.py file, which contains the SAML settings configuration for the SP application.

from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST
from saml2.saml import NAMEID_FORMAT_PERSISTENT




BASE_URL = "http://localhost:8443"


CONFIG = {
   'entityid': BASE_URL + '/saml/metadata/',
   'service': {
       'sp': {
           'name': 'SaaS Sample App',
           "name_id_policy_format": "urn:oasis:names:tc:SAML:2.0: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,
       },
   },
   # Additional configurations ...
   'metadata': {
       'local': ['conf/idp-metadata.xml']
   }
}

In the above file, we have defined the following elements:

  • metadata: This links to the idp-metadata.xml placed under the conf folder.
  • assertion_consumer_service: This refers to the endpoint to which the SAML response would be directed. In this case, it is /saml/acs/.
  • entityid: This defines the SP metadata URL, which is /saml/metadata/ in this case.

Note: If you’re using any other path for idp-metadata or application port, you need to configure it here and update the Okta app integration if there are any changes.

In our main.py, we have configured the saml_client_for function, which provides the SAML client with the defined configuration. 

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 accesses the home page and clicks on login with SSO, the login() function gets triggered at route /login/, which gets the IdP login page URL from the metadata and prepares the SAML Authentication Request.

@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

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

Below is our ACS service, which handles the SAML response coming from 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)


       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}")


       # Accessing attributes directly from the assertion
       attributes = {}
       for statement in authn_response.assertion.attribute_statement:
           for attribute in statement.attribute:
               # Assuming single value attributes for simplicity
               attributes[attribute.name] = attribute.attribute_value[0].text




        # 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', None)
       session['lastname'] = attributes.get('lastname', None)
       session['is_authenticated'] = True
       session['email'] = attributes.get('email', None)
       session['profileUrl']= attributes.get('profileUrl',None)
       session['attributes'] = attributes


       return redirect(url_for('hello'))


   except Exception as e:
       # Log the exception for debugging
       debug_log(f"SAML ACS Error: {e}")


       # Clear any existing session and display error
       session.clear()
       return f"EXCEPTION {e}", 403

Now that the login flow is complete, let us create a basic logout flow as below:

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

When the user clicks on the logout button, it sends a request at /logout/, which will invalidate the user’s session and redirect the user to the home page. 

This is how our home URL function looks like: it renders a template.html file with input provided.

@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 allows users to log in to the application using Okta.

Execution 

To execute this app, start the Flask server:

$ python main.py

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

Loging with SAML SSO Sample SaaS app

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

Login with SAML SSO

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

SAML SSO Credentials

Okta should send a SAML response to the ACS service endpoint on the SP application  after clicking login and entering details. The ACS endpoint should redirect you to home with the logged-in session with the below details. 

The SAML tracer extension, available for Firefox and Chrome, provides details behind the scenes.

Below is the SAML Request sent to IdP:

SAML request to IdP

Below is the SAML response from IdP:

SAML response from IdP

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

SAML successful Login

That’s how you configure Okta SSO SAML for a web application. You can replicate this on any other website.

SAML Best Practices 

The example we went through was a simple web app that was integrated with SAML 2.0 SSO for development purposes. However, production setups are more complex, with many applications, users, and workflows. 

Here are some best practices for implementing SAML 2.0 that you can adopt:

  • Enable Logging: Implementing logging to track authentication attempts, failures, and other security issues. This helps with auditing and compliance.
  • Validate Schemas: Ensure that SAML messages conform to the defined XML schemas to prevent XML signature wrapping attacks and other vulnerabilities.
  • Using Secure Libraries: Leverage secure and up-to-date XML libraries to mitigate vulnerabilities like XML External Entity (XXE) attacks.
  • Using Canonicalized XML: To prevent signature wrapping attacks, use Canonicalized XML documents before verifying signatures.
  • Handle  XML-signed Certificates: Validate and enforce certificate trust chains, revocation checks, and other certificate-related security measures.
  • Choosing the Right Certificate: Use only SHA-2 certificates when designing new SAML workflows; don’t use self-signed certificates.
  • Limit XML Parser: Restrict the XML parser's capabilities to prevent potential vulnerabilities like XXE and other attacks.
  • Use TLS: TLS v1.2 is the preferred protocol when establishing a connection to the SP.
  • Encrypted Assertion: The SAML Assertion should be encrypted.
  • Leverage SingleLogout if using Okta: Okta's Single Logout (SLO) feature allows a user to automatically sign out of all other SLO-participating apps on other devices.

Conclusion

A centralized identity store like Okta is no longer an option but a necessity for enterprises. It is equally important for SaaS applications to support such IdPs that other organizations' IT departments trust and feel comfortable using. Today, companies deal with multiple B2B SaaS applications, making a centralized identity crucial for streamlining access and management. Enabling and integrating IdPs into your applications provides a good user experience, such as one-click sign-in (SSO).

While Okta is a popular provider, others like Auth0 are also available. Some organizations may prefer Okta, while others might choose different IdPs. Configuring each IdP in a SaaS application can be a tedious process and is prone to errors and security issues.

This is where Scalekit can help. Designed for SaaS engineering teams, Scalekit enables quick integration of authentication into apps using SDKs and APIs, making it a reliable choice for SAML integrations in applications.

Apply for early access to Scalekit and ship enterprise authentication in days.

No items found.
Ship Enterprise Auth in days

Ship enterprise auth in hours

Integrate SSO in a few hours

Add major identity providers instantly. Support SAML and OIDC protocols
Talk to our team today to:
Get a personalized demo of Scalekit
Learn how to add SSO to your SaaS app
Get answers for technical integration and setup

Integrate SSO in a few hours

email icon

Talk to you soon!

Thank you for signing up for our early access. Our team will be in touch with you soon over email.
Oops! Something went wrong while submitting the form.

More Related Posts

Text Link