Configure SAML SSO with Okta

Build a SAML SSO integration between Okta and a Node.js app.

Introduction

30 Second Summary

Every time you sign in to a work app and it redirects you to your company's login page instead of asking for a separate password, that's Single Sign-On in action. Behind the scenes, a protocol called SAML 2.0 is doing the heavy lifting.

In this project, you will build a complete SAML SSO integration between Okta (an Identity Provider) and a local Node.js app (a Service Provider). You'll configure both sides of the trust relationship, trigger a real login flow, and inspect the signed XML assertion that proves your identity.

What You'll Build

A Node.js Express application integrated with Okta via SAML 2.0, demonstrating the full enterprise SSO authentication flow from login redirect to signed assertion validation.

By the end of this project, you'll have:

  • A Node.js Express app configured as a SAML Service Provider with login, callback, and metadata routes.
  • An Okta SAML application configured as the Identity Provider with entity IDs, certificates, and attribute statements.
  • A working end-to-end SSO login flow you can trigger, observe in SAML-tracer, and understand at the XML level.
  • Secret Mission: Add custom attribute mapping to pass user profile data from Okta into your app's session.

Are there any prerequisites?

No prior experience with SAML or identity management is needed. This guide walks you through setting up every tool from scratch, including Node.js and your Okta account.

Before We Start

Every enterprise app you sign in to at work likely uses SAML Single Sign-On behind the scenes. To build your own SSO integration, you need a few tools set up first.

In this step, you'll install Node.js (the runtime for your app), add a Chrome extension for inspecting SAML messages, and create your project with all its dependencies.

In this step, get ready to:
  • Install Node.js LTS.
  • Install the SAML-tracer Chrome extension.
  • Create and initialize the project with dependencies.
Open your terminal
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
Check if Node.js is installed

Your Express app needs Node.js to run. This project requires version 24 or higher.

  • Check if Node.js is already installed by running this command:
node --version

✔️ I see version 24 or higher

You're all set. Move on to the next substep.

ⓧ I see an older version

Your installed version is too old for this project's dependencies. You need version 24 or higher.

  • Download the latest LTS installer from https://nodejs.org/en/download.
  • Run the .pkg installer and follow the prompts.
  • Close and reopen your Terminal, then run node --version again to confirm it shows v24 or higher.

Why close and reopen Terminal?

macOS caches the path to executables in your current shell session. Reopening Terminal forces it to pick up the newly installed version.

ⓧ Command not found

Node.js is not installed on your machine yet. Let's install it now.

  • Download the LTS installer from https://nodejs.org/en/download.
  • Run the .pkg installer and follow the prompts to completion.
  • Close and reopen your Terminal, then run node --version to confirm it shows v24 or higher.
  • Confirm npm is also available by running:
npm --version

You should see a version number printed. npm is the package manager that ships with Node.js.

npm command not found?

If node --version works but npm does not, your Node.js installation may be incomplete. Re-run the installer from nodejs.org and make sure you complete all steps.

Need help? Why is npm not found after installing Node.js?

Install the SAML-tracer Chrome extension

SAML-tracer lets you inspect the SAML messages exchanged between your app and Okta during login. You'll use it in a later step to see exactly what data is being sent.

  • In Chrome, navigate to the SAML-tracer extension page.
  • Click Add to Chrome.
  • In the confirmation dialog, click Add extension.
  • Verify the SAML-tracer icon appears in your Chrome toolbar (the puzzle piece area for extensions).

Pin the extension

Click the puzzle piece icon in Chrome's toolbar, then click the pin icon next to SAML-tracer. This keeps it visible so you can activate it quickly later.

Create your project directory and install dependencies

Now you'll create the folder for your SAML Service Provider app and install the packages it needs.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir saml-sso-project
cd saml-sso-project
  • Confirm the folder exists by running:
ls

The folder is empty for now. You should see no output from ls since there are no files yet.

  • Open your code editor. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to Desktop, select the saml-sso-project folder, and click Open.

VS Code now shows the empty project folder in its Explorer sidebar.

  • Create a new file by clicking the New File icon at the top of the Explorer sidebar.
  • Name the file package.json and press Enter.
  • Paste the following content into package.json:
{
  "name": "saml-sso-project",
  "version": "1.0.0",
  "description": "SAML SSO Learning Project",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "4.19.2",
    "express-session": "1.19.0",
    "passport": "0.7.0",
    "@node-saml/passport-saml": "5.1.0"
  }
}

What does this file do?

The package.json file defines your project's identity and its dependencies. The dependencies section lists the four packages your app needs.

  • express is the web framework that serves your app.
  • express-session manages login sessions so users stay authenticated.
  • passport is the authentication middleware that plugs into Express.
  • @node-saml/passport-saml adds the SAML protocol support to Passport.
  • Save package.json by pressing Cmd+S (macOS) or Ctrl+S (Windows).

Now you'll install these dependencies using npm.

  • Open VS Code's integrated terminal by pressing Ctrl+` (backtick key, same shortcut on macOS and Windows).
  • Install all the dependencies listed in package.json by running:
npm install
  • Verify the SAML package installed correctly by running:
ls node_modules/@node-saml

You should see passport-saml listed in the output. This confirms all four packages are installed.

Seeing npm errors during install?

Make sure your terminal is inside the saml-sso-project folder (the same folder that contains package.json). Run pwd to check your current directory.

If you see permission errors, try running the install again. Do not use sudo with npm. If issues persist, delete the node_modules folder and run npm install again.

Still stuck? Help me fix npm install errors in my SAML SSO project.

✔️ Awesome, I've got everything!

Great. Double check you've saved package.json and that node_modules appears in your Explorer sidebar.

ⓧ I'd like to double check the full code

Here is the complete package.json you should have:

{
  "name": "saml-sso-project",
  "version": "1.0.0",
  "description": "SAML SSO Learning Project",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "4.19.2",
    "express-session": "1.19.0",
    "passport": "0.7.0",
    "@node-saml/passport-saml": "5.1.0"
  }
}

Your development environment is ready. In the next step, you'll build the Express app that acts as your SAML Service Provider.

Build the Service Provider App

Your project has all its dependencies installed. Now you need the actual application code that uses them.

In this step, you will build a Node.js Express app that acts as a SAML Service Provider. The app handles the SP side of the trust relationship: it knows where to redirect users for login, it has an endpoint to receive the signed assertion back, and it can generate metadata describing itself to the Identity Provider.

In this step, get ready to:
  • Create the Express app with session management and SAML strategy configuration.
  • Add routes for login, callback, metadata, and logout.
  • Test that the server starts and serves the home page.
Set up Express with session and Passport middleware

Before your app can handle SAML authentication, it needs a foundation: a web server, session management to remember logged-in users across requests, and Passport initialized as the authentication layer.

  • Create a new file called app.js in your saml-sso-project folder by running:
touch app.js
  • Open app.js in your code editor and paste the following code:
const express = require("express");
const session = require("express-session");
const passport = require("passport");
const { Strategy: SamlStrategy } = require("@node-saml/passport-saml");

const app = express();

// Session configuration
app.use(
  session({
    secret: "saml-learning-secret-key",
    resave: false,
    saveUninitialized: false,
  })
);

// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());

What does this code do?

The first four lines import the libraries you installed in Step 1. The { Strategy: SamlStrategy } syntax pulls the SAML strategy class out of the @node-saml/passport-saml package.

The session middleware stores a cookie on the user's browser so your app remembers them after they log in. Passport plugs into Express as middleware, ready to handle authentication whenever a route triggers it.

You won't see a change yet. The next chunk configures the SAML strategy itself.

Configure the SAML strategy and session serialization

The SAML strategy tells Passport where to send users for login (the IdP entry point), how to identify your app (the issuer/entity ID), and how to validate the response (the IdP certificate). You will fill in the real values after configuring Okta.

  • Add this code directly below the Passport initialization lines in app.js:
// SAML Strategy configuration
const samlStrategy = new SamlStrategy(
  {
    entryPoint: "your-okta-sso-url-here",
    issuer: "http://localhost:3000",
    callbackUrl: "http://localhost:3000/login/callback",
    idpCert: "your-okta-certificate-here",
  },
  (profile, done) => {
    const user = {
      id: profile.nameID,
      email: profile.nameID,
      nameIDFormat: profile.nameIDFormat,
    };
    return done(null, user);
  },
  (profile, done) => {
    return done(null, profile);
  }
);

passport.use(samlStrategy);

// Serialize/Deserialize user for session
passport.serializeUser((user, done) => {
  done(null, user);
});

passport.deserializeUser((user, done) => {
  done(null, user);
});

What does this code do?

  • entryPoint — Okta's login URL. When a user needs to authenticate, your app redirects them here.
  • issuer — identifies your app to Okta (must match what's registered on Okta's side).
  • callbackUrl — where Okta sends the user back after they log in, along with a SAML response (a signed XML assertion saying "this user is who they claim to be").
  • idpCert — Okta's public certificate, used to verify that the SAML response really came from Okta and wasn't forged.
  • The verify callback extracts the user's email from profile.nameID and stores it in the session.
  • serializeUser and deserializeUser tell Passport how to save and retrieve the user object from the session.

You won't see a change yet. The next chunk adds the routes your app needs to handle requests.

Add routes for the SAML flow

Your app needs five routes: a home page to show login status, a login trigger, a callback endpoint to receive Okta's response, a metadata endpoint so Okta can read your SP's configuration, and a logout route.

  • Add all the route handlers and the server start below your serialize/deserialize code in app.js:
// Routes

// Home page
app.get("/", (req, res) => {
  if (req.isAuthenticated()) {
    res.send(
      `<h1>SAML SSO Demo</h1>
       <p>Logged in as: <strong>${req.user.email}</strong></p>
       <a href="/logout">Logout</a>`
    );
  } else {
    res.send(
      `<h1>SAML SSO Demo</h1>
       <p>You are not logged in.</p>
       <a href="/login">Login with SSO</a>`
    );
  }
});

// Initiate SAML login
app.get(
  "/login",
  passport.authenticate("saml", { failureRedirect: "/" })
);

// SAML callback (ACS endpoint)
app.post(
  "/login/callback",
  express.urlencoded({ extended: false }),
  passport.authenticate("saml", { failureRedirect: "/" }),
  (req, res) => {
    res.redirect("/");
  }
);

// SP Metadata endpoint
app.get("/metadata", (req, res) => {
  res.type("application/xml");
  res.send(samlStrategy.generateServiceProviderMetadata());
});

// Logout
app.get("/logout", (req, res) => {
  req.logout(() => {
    res.redirect("/");
  });
});

// Start server
app.listen(3000, () => {
  console.log("SAML SP running on http://localhost:3000");
});

What do these routes do?

  • GET / checks if the user is authenticated and shows either a welcome message or a login link.
  • GET /login triggers Passport's SAML strategy, which redirects the browser to Okta's login page.
  • POST /login/callback is the ACS endpoint. Okta sends the signed SAML assertion here after the user authenticates.
  • GET /metadata generates SP metadata XML that describes your app's SAML configuration to Identity Providers.
  • GET /logout destroys the session and redirects back to the home page.
  • Save app.js.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete app.js file for comparison:

const express = require("express");
const session = require("express-session");
const passport = require("passport");
const { Strategy: SamlStrategy } = require("@node-saml/passport-saml");

const app = express();

// Session configuration
app.use(
  session({
    secret: "saml-learning-secret-key",
    resave: false,
    saveUninitialized: false,
  })
);

// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());

// SAML Strategy configuration
const samlStrategy = new SamlStrategy(
  {
    entryPoint: "your-okta-sso-url-here",
    issuer: "http://localhost:3000",
    callbackUrl: "http://localhost:3000/login/callback",
    idpCert: "your-okta-certificate-here",
  },
  (profile, done) => {
    const user = {
      id: profile.nameID,
      email: profile.nameID,
      nameIDFormat: profile.nameIDFormat,
    };
    return done(null, user);
  },
  (profile, done) => {
    return done(null, profile);
  }
);

passport.use(samlStrategy);

// Serialize/Deserialize user for session
passport.serializeUser((user, done) => {
  done(null, user);
});

passport.deserializeUser((user, done) => {
  done(null, user);
});

// Routes

// Home page
app.get("/", (req, res) => {
  if (req.isAuthenticated()) {
    res.send(
      `<h1>SAML SSO Demo</h1>
       <p>Logged in as: <strong>${req.user.email}</strong></p>
       <a href="/logout">Logout</a>`
    );
  } else {
    res.send(
      `<h1>SAML SSO Demo</h1>
       <p>You are not logged in.</p>
       <a href="/login">Login with SSO</a>`
    );
  }
});

// Initiate SAML login
app.get(
  "/login",
  passport.authenticate("saml", { failureRedirect: "/" })
);

// SAML callback (ACS endpoint)
app.post(
  "/login/callback",
  express.urlencoded({ extended: false }),
  passport.authenticate("saml", { failureRedirect: "/" }),
  (req, res) => {
    res.redirect("/");
  }
);

// SP Metadata endpoint
app.get("/metadata", (req, res) => {
  res.type("application/xml");
  res.send(samlStrategy.generateServiceProviderMetadata());
});

// Logout
app.get("/logout", (req, res) => {
  req.logout(() => {
    res.redirect("/");
  });
});

// Start server
app.listen(3000, () => {
  console.log("SAML SP running on http://localhost:3000");
});
Start the server and verify it works

Your app is complete enough to start. The login flow won't work until Okta is configured, but the server should boot and serve the home page without errors.

  • Start the server by running this command in your terminal:
node app.js
  • You should see this output in your terminal:
SAML SP running on http://localhost:3000
  • Open Chrome and navigate to http://localhost:3000. You should see a heading that says "SAML SSO Demo" with a "Login with SSO" link below it.
  • Now navigate to http://localhost:3000/metadata. You should see XML content in the browser. This confirms @node-saml/passport-saml is loaded and generating SP metadata correctly.

Server won't start or you see an error?

  • If you see Cannot find module: make sure you are running the command from inside your saml-sso-project folder where node_modules lives.
  • If you see a syntax error: double check your app.js against the full code in the tab above. Missing commas or brackets are the most common cause.
  • If port 3000 is already in use: stop whatever process is using it, or check that you don't have another terminal running node app.js already.

My server won't start and I need help debugging the error.

💡 Why doesn't the login link work yet?

Clicking "Login with SSO" right now would fail because the entryPoint still points to a placeholder string. Your app doesn't know where Okta's login page is yet. You will fix this in Step 4 after creating the Okta application.

Your Service Provider app is running and ready to receive SAML assertions. Next up, you will create the Identity Provider side in Okta and configure it to trust your app.

Configure Okta as the Identity Provider

Your Service Provider app is running, but it has no idea who to trust for authentication. Right now the entryPoint and idpCert values in your code are just placeholder strings.

In this step, you will set up Okta as your Identity Provider. This is the other half of the SAML trust relationship. You will create a SAML application inside Okta, configure it to point back at your local app, and collect the metadata your SP needs.

In this step, get ready to:
  • Create an Okta account and sign into the Admin Console.
  • Create a SAML 2.0 application integration in Okta.
  • Collect the IdP SSO URL, Issuer, and X.509 certificate for your SP.
Sign up for Okta and create the SAML app integration

Okta offers a free plan for developers and integrators. You will use this to create your Identity Provider.

  • Open Chrome and navigate to https://developer.okta.com.
  • Click Sign up free and create your account. Okta will create an Integrator Free Plan org for you.
  • Once your org is created, sign in to the Admin Console. If you are on the main dashboard, click Admin in the upper-right corner of the page.
  • In the left sidebar, click Applications.
  • Click Applications again in the expanded menu.
  • Click Create App Integration.
  • In the Sign-in method section, select SAML 2.0.
  • Click Next.

What is an Integrator Free Plan?

Okta's Integrator Free Plan gives you a non-production org with up to 10 active users and unlimited SAML apps. It is free and does not require a credit card.

This is more than enough for building and testing SAML integrations.

Configure the SAML integration settings

The wizard walks you through three pages: General Settings, Configure SAML, and Feedback. The Configure SAML page is where you tell Okta about your Service Provider.

  • On the General Settings page, enter SAML Learning App in the App name field.
  • Click Next to move to the Configure SAML page.
  • In the Single sign-on URL field, enter http://localhost:3000/login/callback.
  • In the Audience URI (SP Entity ID) field, enter http://localhost:3000.
  • Set the Name ID format dropdown to EmailAddress.
  • Set the Application username dropdown to Email.
  • Scroll down to the Attribute Statements (optional) section.
  • In the first row, enter email in the Name field.
  • Set the Value field to user.email.
  • Click Next.
  • On the Feedback page, select I'm an Okta customer adding an internal app and click Finish.

What do these SAML settings mean?

The Single sign-on URL is your app's Assertion Consumer Service (ACS) endpoint. This is where Okta sends the SAML response after the user authenticates.

The Audience URI is your app's Entity ID. It uniquely identifies your Service Provider to Okta.

The Attribute Statement tells Okta to include the user's email address inside the SAML assertion. Your app reads this to know who logged in.

🙋‍♀️ Don't see Attribute Statements?

Scroll down on the Configure SAML page. The Attribute Statements section is below the main SAML settings and can require scrolling to find.

help me find the Attribute Statements section in Okta's SAML configuration wizard

Collect IdP metadata and assign your user

After clicking Finish, Okta creates your SAML app and drops you on the app's settings page. Now you need to collect three pieces of information that your SP needs to trust this IdP.

  • Click the Sign On tab.
  • Scroll down to the Metadata details section (or click More details if prompted).
  • Copy the Sign on URL value (the Identity Provider Single Sign-On URL). Save it somewhere you can paste from later.
  • Copy the Issuer value (the Identity Provider Issuer). Save this as well.
  • Find the Signing Certificate section and click the download link to download the X.509 certificate file.

Now assign your own user to the app so you can test SSO with it.

  • Click the Assignments tab.
  • Click Assign, then select Assign to People.
  • Find your user in the list and click Assign next to it.
  • Click Save and Go Back.
  • Click Done.

Why assign yourself?

Okta only issues SAML assertions for users who are assigned to the application. If you skip this step, Okta will reject your login attempt in the next step with an access denied error.

🙋‍♀️ Can't find the certificate download?

On the Sign On tab, look for a View SAML Setup Instructions link or scroll to the SAML Signing Certificates section. You can also click the Actions dropdown next to the SHA-2 certificate entry and select View IdP metadata to access the certificate.

help me find where to download the X.509 certificate from Okta's Sign On tab

You now have both sides of the trust relationship defined. Okta knows about your SP (its ACS URL and Entity ID), and you have the three values your SP needs (SSO URL, Issuer, certificate). Next, you will paste those values into your app to complete the connection.

Connect IdP and SP

Your Service Provider app is running and your Identity Provider is configured in Okta. Right now they don't know about each other.

In this step, you'll copy Okta's metadata values into your app to establish the trust relationship. Once both sides share the same endpoints and certificate, your SP can verify that incoming SAML assertions genuinely came from your IdP.

In this step, get ready to:
  • Paste Okta's IdP SSO URL and X.509 certificate into your Express app.
  • Verify that the Entity ID and callback URL match between IdP and SP.
  • Restart your server and confirm the metadata endpoint generates valid XML.
Copy Okta's SAML values into your app

Your app currently has placeholder strings where the real IdP values need to go. You'll replace these with the actual SSO URL and certificate from Okta so your SP knows where to send authentication requests and how to verify responses.

  • Switch back to the Okta Admin Console in your browser.
  • Click Applications in the left sidebar, then click Applications again.
  • Click on SAML Learning App to open it.
  • Click the Sign On tab.
  • Scroll down to the SAML Signing Certificates section and click View SAML Setup Instructions.

A new page opens with your IdP metadata. You'll see three values listed.

  • Find the Identity Provider Single Sign-On URL value. Copy this entire URL.
  • In your code editor, open app.js and find this line in the SAML Strategy configuration:
entryPoint: "your-okta-sso-url-here",
  • Replace your-okta-sso-url-here with the Identity Provider Single Sign-On URL you just copied. Keep the surrounding quotes.

Now you'll add the certificate. This lets your app verify that SAML responses actually came from Okta and weren't tampered with.

  • Switch back to the SAML Setup Instructions page in your browser.
  • Find the X.509 Certificate section. Copy the entire block of base64 text.
  • In app.js, find this line:
idpCert: "your-okta-certificate-here",
  • Replace your-okta-certificate-here with the certificate text you copied. Keep the surrounding quotes.

What about the BEGIN/END CERTIFICATE lines?

Okta's setup instructions page shows just the base64 certificate content without the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- wrapper lines. If you downloaded the certificate as a file instead, open it in a text editor and copy only the base64 block between those lines.

💡 Why does the SP need the IdP's certificate?

When Okta sends a SAML response, it digitally signs the XML with its private key. Your app uses the public X.509 certificate to verify that signature. If the signature doesn't match, the response is rejected. This prevents anyone from forging a SAML assertion and pretending to be Okta.

Verify the entity ID and callback URL match

SAML validation fails silently when the SP's configured values don't match what the IdP expects. A single character difference in the Entity ID or callback URL causes the entire flow to break.

  • In app.js, confirm the issuer value is set to http://localhost:3000. This must match the Audience URI (SP Entity ID) you entered in Okta exactly.
  • Confirm the callbackUrl value is set to http://localhost:3000/login/callback. This must match the Single sign on URL you entered in Okta exactly.

Check for trailing slashes

A common mistake is having http://localhost:3000/ in one place and http://localhost:3000 in the other. SAML treats these as different values. Make sure neither the issuer nor the Audience URI in Okta has a trailing slash.

Restart the server and check the metadata endpoint

With the trust values in place, your app can now generate valid SP metadata. This metadata is a machine-readable XML document that tells an IdP everything it needs to know about your Service Provider.

  • Switch to the terminal where your server is running.
  • Stop the running server by pressing Ctrl+C.
  • Restart the server by running:
node app.js
  • Open Chrome and navigate to http://localhost:3000/metadata.

You should see XML output in your browser. Look for two key values in the XML that confirm your configuration is correct.

  • Find the entityID attribute near the top. It should show http://localhost:3000.
  • Find the AssertionConsumerService element. Its Location attribute should show http://localhost:3000/login/callback.

What is SP metadata?

SP metadata is a standardized XML document that describes your Service Provider's SAML configuration. It includes your entity ID, your ACS URL, and optionally a signing certificate. In production, you'd send this file to the IdP administrator so they can configure the trust without manually entering each value.

🙋‍♀️ Server won't start or metadata shows errors?

If you see a syntax error when starting the server, check that the certificate string doesn't contain any line breaks inside the quotes. The entire base64 block must be on one line between the quote characters.

If the metadata page shows an error or doesn't load, confirm that both entryPoint and idpCert are wrapped in quotes and that you haven't accidentally deleted a comma between the config properties.

Help me fix my SAML configuration in app.js so the server starts correctly.

Your IdP and SP now share the same endpoints and certificate. The trust relationship is established. Next up, you'll test the full SSO login flow and inspect the actual SAML assertion that Okta sends back.

Test SSO and Inspect the SAML Assertion

Your Identity Provider and Service Provider are now configured to trust each other. The real test is whether clicking "Login with SSO" in your app sends you to Okta, and whether Okta sends back a signed assertion that your app accepts.

In this step, you'll trigger the full SSO flow and use SAML-tracer to capture the actual XML documents exchanged between your browser, Okta, and your app. This is where the protocol becomes visible.

In this step, get ready to:
  • Trigger the SSO login flow and authenticate through Okta.
  • Verify your app recognizes the authenticated user.
  • Inspect the SAML assertion XML to understand what data is exchanged.
Open SAML-tracer and trigger the SSO login

SAML-tracer captures HTTP requests and decodes any SAML messages it finds. You need it running before you click login so it can record the full exchange from the start.

  • In Chrome, click the SAML-tracer icon in your browser toolbar to open the capture window.
  • You can also press Alt+Shift+S to open it with a keyboard shortcut.
  • Leave the SAML-tracer window open. It will record all traffic in the background.

What is SAML-tracer?

SAML-tracer is a Chrome extension that intercepts HTTP requests and automatically decodes any base64-encoded SAML messages it finds. Without it, you'd have to manually extract and decode the SAML payloads from POST parameters.

  • Navigate to http://localhost:3000 in Chrome.
  • Click the Login with SSO link on the home page.

Your browser redirects to Okta's login page. This is the AuthnRequest in action. Your app just sent a SAML authentication request to Okta asking it to verify who you are.

  • Sign in with the Okta credentials you used when creating your Okta account.

What happens when you click Login with SSO?

Your app's /login route triggers Passport's SAML strategy, which builds an AuthnRequest XML document. It base64-encodes this request and redirects your browser to Okta's entryPoint URL with the request attached as a query parameter.

Verify the login succeeded

After you authenticate with Okta, it constructs a SAML Response containing a signed assertion about your identity. Okta then redirects your browser back to your app's ACS endpoint with this response.

  • After signing in to Okta, you should be redirected back to http://localhost:3000.
  • The home page now displays "Logged in as:" followed by your Okta email address.
  • You should also see a Logout link instead of the login link.

Seeing an error instead of your email?

Check the SAML-tracer window for the SAML Response. Look at the Status field in the decoded XML. A successful response has a StatusCode of urn:oasis:names:tc:SAML:2.0:status:Success.

If you see a redirect loop or a failure, the most common causes are a mismatched issuer value (must be exactly http://localhost:3000 matching Okta's Audience URI) or a certificate paste error (extra whitespace or missing characters).

help me debug my SAML login failure

Inspect the SAML assertion in SAML-tracer

The real learning happens when you read the actual XML that Okta sent to your app. This is the "signed passport" that proves your identity. Every enterprise SSO flow produces this same structure.

  • Switch to the SAML-tracer window.
  • Look for rows marked with a SAML icon. You should see at least two SAML messages: the outgoing AuthnRequest (your app asking Okta to authenticate) and the incoming Response (Okta's answer).
  • Click on the SAML Response row to select it.
  • Click the SAML tab in the lower pane to see the decoded XML.

You are now looking at the actual SAML assertion XML. Identify these four key sections:

  • Issuer - Okta's entity ID. This tells your app which Identity Provider created this assertion.
  • Subject/NameID - Your email address. This is the identity claim. It's the value your app uses to identify you.
  • Conditions - Contains the audience restriction (must match your app's entity ID) and time validity (NotBefore and NotOnOrAfter timestamps). These prevent replay attacks.
  • AttributeStatement - Contains the email attribute you configured in Okta. This is how additional user data travels from the IdP to your app.

Why does this matter?

In a production environment, your app trusts this assertion because it's digitally signed with Okta's private key. Your app verifies the signature using the X.509 certificate you pasted into idpCert. No one can forge this assertion without Okta's private key.

The Conditions section adds time-bounding. Even if someone intercepted this assertion, it expires within minutes. The audience restriction ensures the assertion can only be used by your specific app.

💡 Save the XML for reference

You can copy the decoded XML from SAML-tracer and save it to a file. It's a useful reference when debugging SAML integrations in the future.

Secret mission

Add Custom Attribute Mapping

Right now your app only knows the user's email after SSO login. In a real enterprise app, you'd want richer profile data like the user's name, department, or role. Can you configure Okta to pass additional attributes through the SAML assertion and display them in your app?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and uses Okta's free Integrator plan, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Express server running on port 3000.
  • Local project folder saml-sso-project on your Desktop.
  • SAML-tracer Chrome extension.
  • Okta SAML application "SAML Learning App" in your Okta Admin Console.

Keep everything running

No action needed. Choose this if you are still actively building or want to experiment further with your SAML integration.

  • Your Express server stays running on port 3000 until you stop it or close your terminal.
  • Your Okta Integrator Free Plan org remains active. It deactivates automatically after 180 days of inactivity.
  • Node.js remains installed on your machine for future projects.

Pause - I'll come back to this later

Shut down running processes to free up your terminal. Your files and Okta configuration stay intact for when you return.

  • In your terminal where the server is running, press Ctrl+C to stop the Express server.
  • Your saml-sso-project folder and Okta app stay in place. Restart anytime with node app.js from inside the project folder.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each step below to fully clean up.

  • Stop the Express server by pressing Ctrl+C in the terminal where it is running.
  • Delete the project folder by running these commands in your terminal:
cd ~/Desktop
rm -rf saml-sso-project
  • Remove SAML-tracer from Chrome. Right-click the SAML-tracer icon to the right of your address bar and select Remove from Chrome. Click Remove to confirm.
  • Delete the Okta SAML application. In the Okta Admin Console, go to Applications, then Applications.
  • Select Deactivate from the dropdown menu beside your "SAML Learning App" integration.
  • Click the INACTIVE tab.
  • From the dropdown menu next to the app, choose Delete.

That's a Wrap!

That's a Wrap!

Nice work! You just built a complete SAML Single Sign-On integration from scratch, connecting a local Node.js app to Okta as an enterprise Identity Provider.

You've learned how to:

  • Built a Node.js Express app acting as a SAML Service Provider and configured Okta as the Identity Provider with a custom SAML 2.0 application.
  • Established a trust relationship between IdP and SP through metadata exchange including entity IDs, X.509 certificates, and endpoint URLs.
  • Inspected real SAML assertions using SAML-tracer, identifying the structure of AuthnRequests and Responses including Issuer, Subject, Conditions, and AttributeStatements.
  • Secret Mission: Configured custom attribute mapping to pass user profile data (first name, last name) through SAML assertions and displayed them in your app.

Ready to quiz yourself?