Blog

How to build SAML SSO with WorkOS, Entra ID, and Node

Step-by-step tutorial that walks you through the necessary steps to add SSO to your app using SAML, Entra ID (Azure AD), Node, and WorkOS.


If you’re building a website or web app for enterprise use, supporting Single Sign-On (SSO) is essential.

In this tutorial, we will go through all the steps required to implement SAML SSO using Entra ID (Azure AD) as the identity provider, Node as the programming language, and WorkOS as the authentication middleware (aka authorization server).

We will walk you through two different implementations: one that uses AuthKit, a customizable login box powered by WorkOS and Radix, and another in which you build your own login box.

While going through this tutorial, please remember that some things might get outdated as products evolve. Dashboards change, and new SDK versions are released every week. If while you follow this tutorial, something is not working for you, please refer to these docs for the most up-to-date guidance:

Prerequisites

To follow this tutorial, you will need the following:

Step 1: Install the SDK

Install the WorkOS Node SDK to your app.

Via npm:

	
npm install @workos-inc/node
	

Via yarn:

	
yarn add @workos-inc/node
	

Via pnpm:

	
pnpm add @workos-inc/node
	

Step 2: Set secrets

To make calls to WorkOS, you must authenticate using the WorkOS API key and client ID. Copy these values from the WorkOS dashboard.

Store the values as managed secrets and pass them to the SDK either as environment variables or directly in your app’s configuration.

Environment variables example:

	
WORKOS_API_KEY='sk_example_123456789'
WORKOS_CLIENT_ID='client_123456789'
	

For more info on how to handle secrets safely see Best practices for secrets management.

Step 3: Configure the Entra ID connection

At WorkOS, SSO connections are enabled and configured at the organization level. An organization is a collection of users that also acts as a container for enterprise features (like SSO). By enabling an SSO connection for a specific organization, you enable the feature for all users who are members of this organization. This way, you can enable features like forcing all users that use a specific email domain to use a specific SSO connection. For more information on organizations and how to use them, see Model your B2B SaaS with organizations.

First, we will configure Entra ID SSO at the WorkOS dashboard, and then we will move on to the Entra ID admin dashboard to finish the configuration.

WorkOS configuration

  1. Go to the WorkOS dashboard and select Organizations in the left-hand nav.
  2. Pick (or create) the organization for which you’d like to configure an Entra ID connection.
  3. Select “Configure manually” under the “Single Sign-On” section.
  4. Pick “Entra ID (Azure AD)” from the dropdown list and click “Create Connection”.

To create an Entra ID connection, you need the following information:

  • ACS URL: An Assertion Consumer Service URL (ACS URL) is an endpoint where an identity provider posts SAML responses (i.e., where the authentication response will be sent after the user is authenticated).
  • SP Entity ID: A Service Provider (SP) Entity ID is a URI used to identify the issuer of a SAML request, response, or assertion. In this case, the entity ID is used to communicate that WorkOS will be performing SAML requests to the organization’s Entra IDinstance.
  • IdP Metadata URL: An Identity Provider Metadata (IdP Metadata) is the URL or XML file containing all of the metadata relevant to a specific identity provider. It includes attributes used by a service provider to route SAML messages, which minimizes the possibility of a rogue identity provider orchestrating a man-in-the-middle attack. Normally, this information will come from the organization’s IT Management team when they set up your application’s SAML 2.0 configuration in their Entra ID admin dashboard.

WorkOS provides the ACS URL and the SP Entity ID. Right after you create a new Entra ID connection you can see the values in the “Service Provider Details” section. We will use these values soon.

Entra ID configuration

To configure the connection on Entra ID’s side, follow these steps:

  1. Log in: Log in to the Entra ID Active Directory Admin dashboard. Select “Enterprise Applications” from the list of Azure services.
  2. Select or create your application: If your application is already created, select it from the list of Enterprise applications and move to Step 7. If you haven’t created a SAML Application in Azure, select “New Application”.
  3. Initial SAML application setup: Select “Create your own application”, then enter a descriptive app name. Under “What are you looking to do with your application?”, select “Integrate any other application you don’t find in the gallery (Non-gallery)”, then select “Create”. Select “Single Sign-On” from the “Manage” section in the left sidebar navigation menu, and then “SAML”.
  4. Configure SAML Application: Click the Edit icon in the top right corner of the first step “Basic SAML Configuration”.
    • Input the IdP URI (Entity ID) from your WorkOS Dashboard as the “Identifier (Entity ID)”.
    • Input the ACS URL from your WorkOS Dashboard as the “Reply URL (Assertion Consumer Service URL)”.
  5. Configure User Attributes and Claims: Click the Edit icon in the top right corner of the second step “Attributes & Claims”. Make sure the following attribute mapping is set:
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddressuser.mail
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givennameuser.givenname
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameuser.userprincipalname
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surnameuser.surname
  6. Add Users to SAML Application: In order for your users or groups of users to be authenticated, you will need to assign them to your Entra ID SAML application. Select “Users and groups” from the “Manage” section of the navigation menu. Add the selected users and groups of users to your SAML application.
  7. Obtain Identity Provider Details: Select “Single Sign-On” from the “Manage” section in the left sidebar navigation menu. Navigate down to Section 3 of the “Single Sign-On” page, to “SAML Signing Certificate”. Copy the URL provided in “App Federation Metadata URL”.
  8. Update WorkOS: Back in the WorkOS dashboard, click on “Edit Configuration” in the “Identity Provider Configuration” section of the Connection. Input the Metadata URL and click “Save Metadata Configuration”. Your connection will then be linked and good to go!

For more detailed steps and screenshots follow the Entra ID SAML (formerly Azure AD) integration guide.

Step 4: Configure a redirect URI

A redirect URI is the endpoint where the users are redirected after they sign in. We’ll create this endpoint in a bit. For the time being we need to add the URI in the Redirects section of the WorkOS dashboard.

While wildcards in your URIs can be used in the staging environment, they and query parameters cannot be used in production. When users sign out of their application, they will be redirected to your app’s homepage, which is configured in the same dashboard area.

Step 5: Set up the frontend

We are ready to start adding code. In this tutorial we will use React in the frontend but you can use instead Next.js, Remix, or vanilla JS.

To set up the frontend create a simple page with login and logout links. Create a new React app if you don’t have one already, and add the following code to your App.js:

	
export default function App() {
  return (
    <div className="App">
      <h1>SSO example</h1>
      <p>
        <a href="/login">Sign in</a>
      </p>
      <p>
        <a href="/logout">Sign out</a>
      </p>
    </div>
  );
}
	

Step 6: Set up the backend

The authentication process happens in two steps. First, we will start the authentication process by redirecting the user to the identity provider. After the user authenticates, they will be redirected back to the app which will finalize the process by getting the user’s profile an an access token.

At this point you have to decide whether you will use AuthKit, a customizable login box powered by WorkOS and Radix, or build your own login box. Depending on the choice you will use a different API:

SSO with AuthKit

Initiate login

When the user clicks “Sign in”, we need to start the authentication process. We will use the  getAuthorizationUrl  method to generate the authorization URL where the user will be redirected to authenticate.

Add the following code to server.js:

	
const express = require('express');
const { WorkOS } = require('@workos-inc/node');

const app = express();

const workos = new WorkOS(process.env.WORKOS_API_KEY, {
  clientId: process.env.WORKOS_CLIENT_ID,
});

app.get('/login', (req, res) => {
  const authorizationUrl = workos.userManagement.getAuthorizationUrl({
    // Specify that we'd like AuthKit to handle the authentication flow
    provider: 'authkit',

    // The callback endpoint that WorkOS will redirect to after a user authenticates
    redirectUri: 'http://localhost:3000/callback',
    clientId: process.env.WORKOS_CLIENT_ID,
  });

  // Redirect the user to the AuthKit sign-in page
  res.redirect(authorizationUrl);
});
	

After the user authenticates, WorkOS redirects them to the RedirectUri including in the query string the authorization code. The URL would look like this:

	
https://your-app.com/callback?code=g0FGFmNjVmOWIkTGf2PLk4FTYyFGU5
	

Your app needs to extract this code and exchange it for a token (in the next step).

Handle the callback

After the user successfully authenticates, WorkOS will generate a string (the authorization code) and send it back to the app as part of the Redirect URI. The app needs to extract that code and make another call to WorkOS in order to complete the authentication process by exchanging the authorization code for a token and user profile information.

Add the following code to server.js:

	
app.get('/callback', async (req, res) => {
  // The authorization code returned by AuthKit
  const code = req.query.code;
  if (!code) {
    return res.status(400).send('No code provided');
  }

  const { user, access_token } = await workos.userManagement.authenticateWithCode({
    code,
    clientId: process.env.WORKOS_CLIENT_ID,
  });

  // Use the information in `user` for further business logic.

  // Redirect the user to the homepage
  return res.redirect('/');
});
	

The user has now successfully logged in with Entra ID SSO. This is what the response looks like:

	
{
  "user": {
    "object": "user",
    "id": "user_01E4ZCR3C56J083X43JQXF3JK5",
    "email": "marcelina.davis@example.com",
    "first_name": "Marcelina",
    "last_name": "Davis",
    "email_verified": true,
    "profile_picture_url": "https://workoscdn.com/images/v1/123abc",
    "created_at": "2021-06-25T19:07:33.155Z",
    "updated_at": "2021-06-25T19:07:33.155Z"
  },
  "organization_id": "org_01H945H0YD4F97JN9MATX7BYAG",
  "access_token": "eyJhb.nNzb19vaWRjX2tleV9.lc5Uk4yWVk5In0",
  "refresh_token": "yAjhKk123NLIjdrBdGZPf8pLIDvK",
  "impersonator": {
    "email": "admin@foocorp.com",
    "reason": "Investigating an issue with the customer's account."
  }
}
	

The user object can be used for further business login like personalizing the UI for the user.

The response also includes an access token and a refresh token. These two tokens can be used to manage the user’s session without asking them to authenticate all the time. The access token is short-lived and allows an application to access resources on a user’s behalf, while the refresh token, which lives a bit longer, can be used to get a new access token when that expires.

Both tokens should be handled and stored securely since if an attacker manages to obtain a user's token, they can impersonate the user and gain unauthorized access to protected resources. WorkOS SDKs use sealed sessions (i.e., sessions encrypted with a strong password) to keep tokens safe. For more information, see Handle the user session.

SSO without AuthKit

Initiate login

When the user clicks “Sign in”, we need to start the authentication process. We will use the getAuthorizationUrl method to generate the authorization URL where the user will be redirected to authenticate.

Add the following code to server.js:

	
const express = require('express');
const { WorkOS } = require('@workos-inc/node');

const app = express();

const workos = new WorkOS(process.env.WORKOS_API_KEY);
const clientId = process.env.WORKOS_CLIENT_ID;

app.get('/login', (_req, res) => {
  // The ID of the organization associated with the SSO connection.
  // For testing purposes, use the ID of the Test Organization from the dashboard. 
  // Replace it with the real organization ID when you finish the integration.
  const organization = 'org_test_idp';

  // The callback URI WorkOS should redirect to after the authentication
  const redirectUri = 'http://localhost:3000/callback';

  const authorizationUrl = workos.sso.getAuthorizationUrl({
    organization,
    redirectUri,
    clientId,
  });

  res.redirect(authorizationUrl);
});
	

After the user authenticates, WorkOS redirects them to the redirectUri including in the query string the authorization code. The URL would look like this:

	
https://your-app.com/callback?code=g0FGFmNjVmOWIkTGf2PLk4FTYyFGU5
	

Your app needs to extract this code and exchange it for a token (in the next step).

Handle the callback

After the user successfully authenticates, WorkOS will generate a string (the authorization code) and send it back to the app as part of the Redirect URI. The app needs to extract that code and make another call to WorkOS in order to complete the authentication process by exchanging the authorization code for a token and user profile information.

Add the following code to server.js:

	
const express = require('express');
const { WorkOS } = require('@workos-inc/node');

const app = express();
const workos = new WorkOS(process.env.WORKOS_API_KEY);
const clientId = process.env.WORKOS_CLIENT_ID;

app.get('/callback', async (req, res) => {
  // Extract the authorization code from the URL
  const { code } = req.query;
  if (!code) {
    return res.status(400).send('No code provided');
  }

  const { profile, access_token } = await workos.sso.getProfileAndToken({
    code,
    clientId,
  });

  // Use the information in `profile` for further business logic.

  // Redirect the user to the homepage
  res.redirect('/');
});
	

The user has now successfully logged in with Entra ID SSO. This is what the response looks like:

	
{
  "access_token": "01DMEK0J53CVMC32CK5SE0KZ8Q",
  "profile": {
    "object": "profile",
    "id": "prof_01DMC79VCBZ0NY2099737PSVF1",
    "connection_id": "conn_01E4ZCR3C56J083X43JQXF3JK5",
    "connection_type": "EntraIDSAML",
    "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY",
    "email": "todd@example.com",
    "first_name": "Todd",
    "last_name": "Rundgren",
    "idp_id": "00u1a0ufowBJlzPlk357",
    "role": { "slug": "admin" },
    "raw_attributes": {}
  }
}
	

The profile object can be used for further business login like personalizing the UI for the user.

Step 7: Test the connection

To confirm your Single Sign-On integration works correctly you can use the default Test Organization and active SSO connection you can find in the WorkOS dashboard.

To get started, go to the WorkOS dashboard and navigate to the Test SSO page. This page outlines a number of different SSO scenarios you can follow and provides all the necessary information to complete the tests.

  • Service provider-initiated SSO: The test simulates users initiating authentication from your sign-in page. In this scenario, the user enters their email in your app, gets redirected to the identity provider, and then is redirected back to your application. You can do this test with AuthKit or with standalone SSO.
  • Identity provider-initiated SSO: This test simulates users initiating authentication from their identity provider. It is a common login flow that developers forget to consider. In the scenario, users log in to the identity provider directly, select your application from their list of SSO-enabled apps, and are redirected to your application upon successful authentication.

Other options are guest email domain, which simulates users authenticating with an email domain different from the verified domain of the test organization, and error response, which simulates a generic error response from the user’s identity provider.

To run all these steps follow the on-screen instructions.

Next steps

You have now set up an SSO connection and successfully integrated it in your app. This is but the first step in your identity management journey. The next steps are handling the user’s session, implementing logout, adding social logins, implementing access control, provisioning users automatically, handling failed authentication events, and more. Stay tuned for follow up tutorials on all of these areas.In the meanwhile here are some resources you might find useful:

In this article

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.