WorkOS: Add Enterprise SSO to Your SaaS in Hours
TOP 5 April 17, 2026, 5:30 p.m.

WorkOS: Add Enterprise SSO to Your SaaS in Hours

Imagine a SaaS product that instantly speaks the language of Fortune 500 security teams—no custom SAML wiring, no endless back‑and‑forth with IT admins. With WorkOS, you can embed enterprise‑grade Single Sign‑On (SSO) in just a few hours, not weeks. In this guide we’ll walk through the core concepts, walk you through a live integration, and sprinkle in pro tips you won’t find in the official docs.

Why SSO Matters for Modern SaaS

Enterprises demand a single source of truth for authentication. By delegating login to an identity provider (IdP) such as Okta, Azure AD, or OneLogin, they reduce password fatigue, enforce MFA, and stay compliant with regulations like SOC 2 and GDPR.

From a product perspective, SSO is a competitive moat. A smooth, secure login experience can shave days off a sales cycle and lower churn because users stay within the familiar corporate login flow.

Key Benefits

  • Security: Credentials never touch your servers; the IdP handles authentication.
  • Compliance: Leverage the IdP’s audit logs and MFA policies.
  • Productivity: Users log in once and get seamless access to all SaaS tools.
  • Scalability: Adding a new customer is a matter of configuration, not code.

What WorkOS Brings to the Table

WorkOS abstracts the messy details of SAML, OIDC, and SCIM behind a unified API. It provides:

  1. Pre‑built IdP connectors for 30+ providers.
  2. Hosted login pages that can be white‑labeled.
  3. Directory sync (SCIM) for provisioning users automatically.
  4. Audit logs and risk insights out of the box.

The magic lies in its developer‑first approach: you only need a few environment variables and a couple of endpoint handlers to get up and running.

Pro tip: Enable WorkOS’s “auto‑provisioning” feature during the beta phase. It saves you from writing custom SCIM endpoints later.

Getting Started: Prerequisites

Before you write a single line of code, make sure you have:

  • A WorkOS account (sign up at workos.com).
  • API keys (Publishable and Secret) from the WorkOS dashboard.
  • A web framework of your choice (the examples use Flask and Express).
  • HTTPS enabled locally (e.g., using ngrok)—IdPs reject non‑secure callbacks.

Once you’ve collected the keys, store them securely in environment variables:

# .env file
WORKOS_API_KEY=sk_test_XXXXXXXXXXXXXXXX
WORKOS_CLIENT_ID=client_XXXXXXXXXXXXXXXX
WORKOS_REDIRECT_URI=https://your-ngrok-subdomain.ngrok.io/auth/callback

Step 1: Install the SDK

WorkOS offers official SDKs for Python, Node.js, Ruby, and Go. Choose the one that matches your stack.

For Python (Flask example):

pip install workos

For Node.js (Express example):

npm install @workos-inc/node

Step 2: Configure the SDK

Initialize the client with your secret key and set the redirect URI. Keep this configuration in a dedicated module so you can import it everywhere.

Python (Flask)

# workos_client.py
import os
from workos import WorkOS

workos = WorkOS(api_key=os.getenv("WORKOS_API_KEY"))
workos.redirect_uri = os.getenv("WORKOS_REDIRECT_URI")

Node.js (Express)

// workosClient.js
const WorkOS = require("@workos-inc/node").WorkOS;
const workos = new WorkOS(process.env.WORKOS_API_KEY);
workos.redirectURI = process.env.WORKOS_REDIRECT_URI;

module.exports = workos;

Step 3: Create the Login Endpoint

The first user‑facing route redirects the browser to WorkOS’s hosted login page. You can optionally pass a connection ID to pre‑select a specific IdP, but most SaaS products let the user choose.

Flask Example

# app.py (excerpt)
from flask import Flask, redirect, request, session, url_for
from workos_client import workos

app = Flask(__name__)
app.secret_key = "super‑secret‑session‑key"

@app.route("/login")
def login():
    # Generate a state token to mitigate CSRF
    state = workos.utils.generate_state()
    session["workos_state"] = state

    # Build the login URL
    login_url = workos.sso.get_authorization_url(
        client_id=os.getenv("WORKOS_CLIENT_ID"),
        redirect_uri=os.getenv("WORKOS_REDIRECT_URI"),
        state=state,
        # optional: domain_hint="example.com"
    )
    return redirect(login_url)

Express Example

// server.js (excerpt)
const express = require("express");
const session = require("express-session");
const workos = require("./workosClient");
require("dotenv").config();

const app = express();

app.use(session({
  secret: "super‑secret‑session‑key",
  resave: false,
  saveUninitialized: true,
}));

app.get("/login", (req, res) => {
  const state = workos.utils.generateState();
  req.session.workosState = state;

  const loginUrl = workos.sso.getAuthorizationUrl({
    clientId: process.env.WORKOS_CLIENT_ID,
    redirectUri: process.env.WORKOS_REDIRECT_URI,
    state,
    // optional: domainHint: "example.com"
  });

  res.redirect(loginUrl);
});
Pro tip: Persist the state token in a short‑lived cache (Redis) if you run multiple server instances. This avoids session‑affinity issues.

Step 4: Handle the Callback

After the user authenticates with their IdP, WorkOS redirects back to the redirect_uri with a code and the original state. Exchange the code for a user profile.

Flask Callback

@app.route("/auth/callback")
def auth_callback():
    # Verify state token
    received_state = request.args.get("state")
    if received_state != session.get("workos_state"):
        return "Invalid state", 400

    # Exchange code for profile
    code = request.args.get("code")
    profile = workos.sso.get_profile_and_token(code)

    # At this point you have:
    # profile.id, profile.email, profile.first_name, profile.last_name, etc.
    # Store or create a local user record
    user = get_or_create_user(profile)

    # Log the user in (Flask-Login example)
    login_user(user)
    return redirect(url_for("dashboard"))

Express Callback

app.get("/auth/callback", async (req, res) => {
  const { state, code } = req.query;

  // Verify state token
  if (state !== req.session.workosState) {
    return res.status(400).send("Invalid state");
  }

  try {
    const profile = await workos.sso.getProfileAndToken({ code });
    // profile.id, profile.email, profile.first_name, profile.last_name ...

    const user = await getOrCreateUser(profile);
    req.session.userId = user.id; // simple session login
    res.redirect("/dashboard");
  } catch (err) {
    console.error("WorkOS callback error:", err);
    res.status(500).send("Authentication failed");
  }
});

The profile object contains enough data to either match an existing account or provision a new one. Most SaaS products use the IdP‑provided email as the unique identifier.

Step 5: Provision Users with SCIM (Optional but Powerful)

If you want your SaaS to stay in sync with the enterprise directory—adding new hires automatically and removing departing employees—you’ll need SCIM. WorkOS offers a hosted SCIM endpoint that you can enable with a single toggle.

  • Enable “SCIM Provisioning” in the WorkOS dashboard.
  • Define a webhook URL in your SaaS that receives POST /scim/v2/Users events.
  • Map the incoming SCIM payload to your internal user model.

SCIM Webhook Example (Node.js)

app.post("/scim/v2/Users", express.json(), async (req, res) => {
  const event = req.body; // WorkOS sends a standard SCIM payload

  if (event.operation === "create") {
    await createUserFromScim(event);
  } else if (event.operation === "delete") {
    await deactivateUser(event.id);
  } else if (event.operation === "replace") {
    await updateUser(event);
  }

  // Respond with 200 to acknowledge receipt
  res.sendStatus(200);
});

Once enabled, any change in the corporate directory instantly reflects in your SaaS—no manual admin work required.

Pro tip: Store the SCIM id returned by WorkOS alongside your internal user ID. It makes future de‑provisioning idempotent and safe.

Real‑World Use Cases

1. B2B Analytics Platform – A startup needed to onboard enterprise customers quickly. By integrating WorkOS, they reduced the onboarding timeline from 2 weeks (manual SAML) to 1 day (self‑service SSO). The analytics dashboard now displays a “Connected to Company X” banner, reinforcing trust.

2. HR SaaS with Dynamic Permissions – The product ties user roles to Azure AD groups. WorkOS’s group‑claims feature lets the app read the groups attribute from the IdP token and map it to internal permission sets without a separate API call.

3. Multi‑Tenant Project Management Tool – Using WorkOS’s hosted login, the product offers a single URL (login.myapp.com) that automatically detects the customer’s domain (via the domain_hint) and routes them to the correct IdP. This “domain‑aware” flow eliminates the need for a “Select Your Company” dropdown.

Advanced Customizations

WorkOS is flexible enough to handle edge cases. Below are a few patterns you might encounter.

Custom Branding on the Hosted Login Page

  • Upload your logo and set brand colors in the WorkOS dashboard.
  • Pass a login_hint parameter to pre‑populate the email field.

Example (Python):

login_url = workos.sso.get_authorization_url(
    client_id=os.getenv("WORKOS_CLIENT_ID"),
    redirect_uri=os.getenv("WORKOS_REDIRECT_URI"),
    state=state,
    login_hint="alice@yourcompany.com"
)

Handling Multiple IdPs per Tenant

Some enterprises use both Okta and Azure AD for different divisions. WorkOS lets you create multiple connections and store the connection ID per tenant.

# When rendering the login button
connection_id = tenant.workos_connection_id  # fetched from your DB
login_url = workos.sso.get_authorization_url(
    client_id=os.getenv("WORKOS_CLIENT_ID"),
    redirect_uri=os.getenv("WORKOS_REDIRECT_URI"),
    state=state,
    connection=connection_id
)

Enforcing MFA via WorkOS Risk Engine

WorkOS can flag high‑risk logins (new device, unusual location). You can inspect the risk_score in the profile response and prompt for an additional step.

profile = workos.sso.get_profile_and_token(code)
if profile.risk_score > 80:
    # Redirect to a secondary verification flow
    return redirect(url_for("mfa_challenge"))

Testing Your Integration

Before you push to production, run through these sanity checks:

  1. Happy Path: Log in with a test Okta account, verify the user record is created, and confirm the session persists.
  2. State Mismatch: Manually tamper with the state query param and ensure your app rejects the request.
  3. SCIM Sync: Add a user in Azure AD, watch the webhook fire, and confirm the user appears in your SaaS.
  4. Logout Flow: Invalidate the session and optionally call WorkOS’s /logout endpoint to terminate the IdP session.

WorkOS provides a sandbox environment with mock IdPs, perfect for CI pipelines. Use the WORKOS_API_BASE_URL environment variable to point your SDK at the sandbox.

Performance & Scaling Considerations

Because the heavy lifting (authentication, token validation) happens on WorkOS’s servers, your SaaS only incurs a lightweight HTTP call per login. Still, keep these best practices in mind:

  • Cache the JWKs: WorkOS signs tokens with RSA keys. Cache the JSON Web Key Set for up to 24 hours to avoid repeated network fetches.
  • Rate‑limit callback endpoints: Prevent denial‑of‑service attacks on /auth/callback by applying a modest request‑per‑minute limit.
  • Stateless sessions: Consider JWT‑based sessions after the initial SSO exchange to reduce DB reads.

Security Checklist

Even though WorkOS handles most security concerns, you still own the surrounding infrastructure.

  1. Serve all endpoints over HTTPS (including local development with ngrok).
  2. Validate the state token to prevent CSRF.
  3. Store the WorkOS secret key in a secret manager (AWS Secrets Manager, Vault, etc.).
  4. Implement least‑privilege access for the SCIM webhook URL (IP allow‑list, HMAC verification).
  5. Log audit events when users log in or are provisioned; tie them to WorkOS’s audit API for a unified view.

Monitoring & Observability

WorkOS emits detailed logs and webhook events. Pair them with your own telemetry to get end‑to‑end visibility.

  • Metrics: Track login_success, login_failure, and scim_sync counts.
  • Tracing: Include the WorkOS request ID in your logs to correlate backend processing with the upstream SSO call.
  • Alerts: Set thresholds for abnormal spikes in risk scores or failed login attempts.

Deploying to Production

When you’re ready to go live, swap the sandbox API key for the production one and update the redirect URIs in the WorkOS dashboard to match your production domain.

Don’t forget to:

  1. Enable “Enforce TLS 1.2+” in the dashboard.
  2. Review the “Allowed Callback URLs” list—any stray URL could be a phishing vector.
  3. Run a final security audit on the SCIM webhook (verify HMAC signatures
Share this article