SSO Documentation | PropelAuth BYO Documentation

SSO Documentation

Your customers are asking:

PropelAuth BYO SSO lets you say “yes” to all of these. You add OpenID Connect (OIDC) support that works with Okta, Azure AD/Entra, Google Workspace, Ping Identity, JumpCloud, and any other OIDC-compliant identity provider.

To add SSO, you need three things:

  1. A way for customers to set up their SSO connection - They give you their IdP details, you store them
  2. Two backend endpoints - One is where users start the SSO flow, the other is where they land after authenticating
  3. A simple frontend flow - Augment your login page to handle SSO

In the end, the user experience will look something like this:

Setting up an SSO Connection

A good way to think about SSO is that you need to set up a login method that only works for one specific customer. To make this work, you need to collect some information from your customer, and they need to configure their IdP to point to your app.

Collecting Information from Your Customer

The information you need to collect from your customer depends on which identity provider they use, but generally includes:

You will also need to provide your customer with a Redirect URL (also known as a Callback URL) that they need to provide to their IdP. This is where users will be redirected to after authenticating with their identity provider.

We recommend providing guides for popular IdPs to help your customers in collecting the correct information and configuring their IdPs to point to your app. See our Example OIDC Setup Guides for inspiration.

Creating the OIDC Client

Once you have collected the necessary information from your customer, you can call the Create OIDC Client function on your backend.

Node Python Go Java .NET

// Your admin endpoint for SSO setup

app.post("/api/admin/sso/setup", async (req, res) => {
    // Get the organization your user is in (your logic)
    const organizationId = getOrganizationIdFromRequest(req);

// Create the OIDC client in PropelAuth BYO
    const result = await auth.sso.management.createOidcClient({
        // This links the SSO config to your customer
        customerId: organizationId,
        // Make sure to provide this value to your customer,
        //   they'll need it when setting up their IdP
        redirectUrl: "https://yourapp.com/api/auth/callback",
        idpInfoFromCustomer: {
            // When idpType is "Okta", we are able to infer some values compared to "Generic"
            // All these values will be provided by your customer
            idpType: "Okta",
            ssoDomain: "acme-corp.okta.com",
            clientId: req.body.clientId,
            clientSecret: req.body.clientSecret,
            usesPkce: true,
        },
    });

if (result.ok) {
        res.json({ success: true });
    } else {
        res.status(400).json({ error: result.error });
    }
});

The Login Flow

Once SSO is set up for an organization, here’s how users log in:

Step 1: User enters their email

Your user enters their email on your login page. You need to map this to a customer/organization ID.

Node Python Go Java .NET

// Your existing logic to map email → organization

const organizationId = await getOrganizationFromEmail(email);

if (!organizationId) {
    // Handle regular login or show error
    return;
}

Step 2: Initiate the SSO flow

Use the Initiate OIDC Login function to start the SSO flow:

Node Python Go Java .NET

const result = await client.sso.initiateOidcLogin({
    // Use the customerId we specified when setting up SSO
    customerId: organizationId,
});

if (!result.ok) {
    if (result.error.type === "ClientNotFound") {
        // This organization doesn't have SSO configured
        // Fall back to regular login
    } else {
        // Handle other errors
    }
    return;
}

// The result contains two important values:
const { stateForCookie, sendUserToIdpUrl } = result.data;

// Set the state as a cookie (needed for CSRF protection)
res.cookie("oidc_state", stateForCookie, {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    maxAge: 5 * 60, // 5 minutes
});

// Redirect user to their IdP
res.redirect(sendUserToIdpUrl);

Step 3: Handle the callback

After the user authenticates at their IdP, they’re redirected to your callback URL. Handle this in your backend with the Complete OIDC Login function:

Node Python Go Java .NET

app.get("/api/auth/callback", async (req, res) => {
    const result = await auth.sso.completeOidcLogin({
        stateFromCookie: req.cookies.oidc_state,
        // BYO takes in the full URL including query params to finish the login
        callbackPathAndQueryParams: req.originalUrl,
    });

if (!result.ok) {
        // Handle various error types
        if (result.error.type === "LoginBlockedByEmailAllowlist") {
            // User's email not in allowlist
        } else if (result.error.type === "ScimUserNotActive") {
            // User deprovisioned via SCIM
        }
        // ... handle other errors
        return;
    }

// Success! You get the user data
    const ssoUser = result.data;
    console.log(ssoUser.email);
    console.log(ssoUser.oidcUserId); // Stable ID from IdP
    console.log(ssoUser.dataFromSso); // Additional claims from IdP
    // Now log them in however you normally would
});

Callback URL Configuration

When your customers set up their OIDC client in their IdP, they need to specify a redirect URL - this is where users land after authenticating:

https://yourapp.com/api/auth/callback

This URL must:

Give customers the exact URL for their environment when they’re setting up SSO.

IdP Info

Included in the Create OIDC Client command is an IdP Info From Customer argument. This argument accepts an object that contains information about the identity provider you are connecting to. In the object is an IdP Type field that accepts one of the following values:

Depending on which you set, the required fields for the IdP Info object may vary.