Skip to content

Handle identity provider initiated SSO

Learn how to securely implement IdP-initiated Single Sign-On for your application

This guide shows you how to securely implement Identity Provider (IdP)-initiated Single Sign-On for your application. When users log into your application directly from their identity provider’s portal, Scalekit converts the IdP-initiated request to a Service Provider (SP)-initiated flow for enhanced security.

Review the authentication sequence IdP-initiated SSO FlowUserIdPScalekitYourApp Select your application from IdP portal Send SAML assertion or OIDC request Redirect to initiate login endpoint with JWT Convert to SP-initiated authorization URL Standard authentication flow Authenticate if needed Send response Callback with authorization code Exchange code for tokens Log in user

The workflow converts the traditional IdP-initiated flow to a secure SP-initiated flow by:

  1. The user logs into their identity provider portal and selects your application
  2. The identity provider sends user details as assertions to Scalekit
  3. Scalekit redirects to your initiate login endpoint with a JWT token
  4. Your application validates the JWT and generates a new SP-initiated authorization URL

To securely implement IdP-initiated SSO, follow these steps to convert incoming IdP-initiated requests to SP-initiated flows:

  1. Set up an initiate login endpoint and register it in Dashboard > Developers > Redirect URLs > Initiate Login URL
  2. Extract information from the JWT token containing organization, connection, and user details
  3. Convert to SP-initiated flow using the extracted parameters to generate a new authorization URL
  4. Handle errors with proper callback processing and error handling best practices

Use the extracted parameters to initiate a new SSO request. This converts the IdP-initiated flow to a secure SP-initiated flow. Here are implementation examples:

Express.js
4 collapsed lines
// Security: ALWAYS verify requests are from Scalekit before processing
// This prevents unauthorized parties from triggering your interceptor logic
// Use case: Handle IdP-initiated SSO requests from enterprise customer portals
// Examples: Okta dashboard, Azure AD portal, Google Workspace apps
const express = require('express');
const app = express();
app.get('/login', async (req, res) => {
try {
// Your Initiate Login Endpoint receives a JWT
const { error_description, idp_initiated_login } = req.query;
if (error_description) {
return res.redirect('/login?error=auth_failed');
}
// Decode the JWT and extract claims
5 collapsed lines
if (idp_initiated_login) {
const {
connection_id,
organization_id,
login_hint,
relay_state
} = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login);
// Use ONE of the following properties for authorization
const options = {};
if (connection_id) options.connectionId = connection_id;
if (organization_id) options.organizationId = organization_id;
if (login_hint) options.loginHint = login_hint;
if (relay_state) options.state = relay_state;
// Generate Authorization URL for SP-initiated flow
const url = scalekit.getAuthorizationUrl(
process.env.REDIRECT_URI,
options
);
return res.redirect(url);
}
// Handle regular login flow here
res.redirect('/login');
} catch (error) {
console.error('IdP-initiated login error:', error);
res.redirect('/login?error=auth_failed');
}
});

Your initiate login endpoint will receive requests with the following format:

Terminal window
https://yourapp.com/login?idp_initiated_login=<encoded_jwt_token>

The idp_initiated_login parameter contains a signed JWT with organization, connection, and user details.

View JWT structure
{
"organization_id": "org_225336910XXXX588",
"connection_id": "conn_22533XXXXX575236",
"login_hint": "name@example.com",
"exp": 1723042087,
"nbf": 1723041787,
"iat": 1723041787,
"iss": "https://b2b-app.com"
}

If errors occur, the redirect URI will receive a callback with this format:

Terminal window
https://{your-subdomain}.scalekit.dev/callback
?error="<error_category>"
&error_description="<details>"

After completing the SP-initiated flow, users are redirected back to your callback URL where you can complete the authentication process. Next, let’s look at how to test your IdP-initiated SSO implementation.

Integrating with a downstream auth provider

Section titled “Integrating with a downstream auth provider”

If your application uses a third-party service like Firebase Authentication to manage user sessions, you must initiate its sign-in flow after completing Step 3.

This process has two stages: first, the IdP redirects the user to your app via Scalekit, and second, your app triggers a new sign-in flow with Firebase using the Authorization URL you just generated.

Review the downstream auth flow Downstream Auth Provider IntegrationUserIdPScalekitYourAppFirebase Select your application Send IdP request Redirect with JWT triggers sign-in uses Authorization URL from Step 3 Standard SP-initiated flow Send response Exchange code for tokens User authenticated Complete login

The example below shows how to pass the Authorization URL to the Firebase Web SDK.

Firebase Web SDK
import { getAuth, OAuthProvider, signInWithRedirect } from "firebase/auth";
// Security: Configure OIDC provider properly to prevent token injection
const auth = getAuth();
// "scalekit" is the OIDC provider you configured in Firebase
const scalekitProvider = new OAuthProvider("scalekit");
// Use the authorizationUrl generated in Step 3
scalekitProvider.setCustomParameters({
connection_id: "<connection_id>", // Enables Firebase to forward the connection ID to Scalekit
});
// Initiate Firebase sign-in with Scalekit provider
signInWithRedirect(auth, scalekitProvider);

While IdP-initiated SSO offers convenience, it comes with significant security risks. Scalekit’s approach converts the flow to SP-initiated to mitigate these vulnerabilities.

Traditional IdP-initiated SSO security risks

Section titled “Traditional IdP-initiated SSO security risks”

Stolen SAML assertions: Attackers can steal SAML assertions and use them to gain unauthorized access. If an attacker manages to steal these assertions, they can:

  • Inject them into another service provider, gaining access to that user’s account
  • Inject them back into your application with altered assertions, potentially elevating their privileges

With a stolen SAML assertion, an attacker can gain access to your application as the compromised user, bypassing the usual authentication process.

Attackers can steal SAML assertions through various methods:

  • Man-in-the-middle (MITM) attacks: Intercepting and replacing the SAML response during transmission
  • Open redirect attacks: Exploiting improper endpoint validation to redirect the SAML response to a malicious server
  • Leaky logs and headers: Sensitive information, including SAML assertions, can be leaked through logs or headers
  • Browser-based attacks: Exploiting browser vulnerabilities to steal SAML assertions

The chief problem with stolen assertions is that everything appears legitimate to the service provider (your application). The message and assertion are valid, issued by the expected identity provider, and signed with the expected key. However, the service provider cannot verify whether the assertions are stolen or not.

If you encounter issues implementing IdP-initiated SSO:

  1. Verify configuration: Ensure your redirect URI is properly configured in Dashboard > Developers > Redirect URLs
  2. Check JWT processing: Verify you’re correctly processing the JWT token from the idp_initiated_login parameter
  3. Validate error handling: Ensure your error handling properly captures and processes any error messages
  4. Test connections: Confirm the organization and connection IDs in the JWT are valid and active
  5. Review logs: Check both your application logs and Scalekit dashboard logs for debugging information