SSO Documentation | PropelAuth BYO Documentation
SSO Documentation
Your customers are asking:
- “Can we use Okta to log in?”
- “Does your product work with Entra/Azure AD?”
- “Do you support SSO?”
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:
- A way for customers to set up their SSO connection - They give you their IdP details, you store them
- Two backend endpoints - One is where users start the SSO flow, the other is where they land after authenticating
- 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:
- Client ID: A public identifier for the client app in the IdP.
- Client Secret: A secret known only to the IdP and the client app.
- IdP: The identity provider (Okta, Entra, or Generic).
- OIDC Endpoints (authorization, token, userinfo): Sometimes you can infer these from the IdP’s domain, other times you need to ask your customer to provide them. See Idp Info below for more information.
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:
- Match exactly what you handle in your backend (from Step 3 above)
- Be publicly accessible
- Use HTTPS in production
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:
OktaMicrosoftEntraGeneric
Depending on which you set, the required fields for the IdP Info object may vary.