Implement SSO
Add SAML/OIDC, IdP-initiated login, and the admin portal. Then stop.
Guardrails
- MUST pass one selector on every authorization URL:
connectionId,organizationId, orloginHint. - MUST keep
redirectUriidentical to the dashboard Allowed callback URL. - MUST call
validateTokenbefore using claims. - MUST keep
relay_stateasstateon IdP-initiated login. - MUST print dashboard checklists, wait for the user, then continue. Do not click the dashboard.
Gotchas
- Modular SSO is for apps that already manage users and sessions. Scalekit handles SAML/OIDC. The app keeps the user store and session.
- Full-Stack Auth / SaaSKit login already includes SSO. Name
implement-saaskitand stop. Modular SSO or FSA, not both. - Auth mode: Dashboard → Settings → Authentication Mode → Modular Auth. Path is
/settings/authentication-mode. - Read
SCALEKIT_ENVIRONMENT_URL,SCALEKIT_CLIENT_ID,SCALEKIT_CLIENT_SECRET. NeverSCALEKIT_ENV_URL. Do not prependhttps://if the URL already has a scheme. - Default language is Node. Default scopes are
openid profile email. Modular SSO does not supportoffline_access. Do not write refresh tokens or SaaSKit cookies. - Selector order:
connectionId>organizationId>loginHint.domainHintalso works. Without a selector Scalekit cannot pick an IdP. - Copy organization IDs from the dashboard. Do not invent
org_…values. - Test proof is Organizations → Test Organization. Do not ask for FSA dryrun or a hosted-login URL.
- IdP click-paths live in references/providers.md. Point IT at the live guide.
Step 1 — Confirm Modular Auth
If the app already uses SaaSKit or Full-Stack Auth login, name implement-saaskit and stop.
Print this checklist. Wait for the user. Then continue.
- app.scalekit.com → Settings → Authentication Mode → Modular Auth
- Developers → Settings → API Credentials. Copy the three env names.
- Authentication → Redirects:
- Allowed callback URLs: the app
/auth/callback(local defaulthttp://localhost:3000/auth/callback) - Initiate login URL: the app
/login(local defaulthttp://localhost:3000/login)
- Allowed callback URLs: the app
Put the three env names in the project env file. Do not invent values.
Done when: mode is Modular Auth, the three env names exist, and both redirect URLs are registered.
Step 2 — Init the SDK
Install @scalekit-sdk/node only when the repo has no Scalekit SDK yet.
import { ScalekitClient } from '@scalekit-sdk/node';
const scalekit = new ScalekitClient(
process.env.SCALEKIT_ENVIRONMENT_URL,
process.env.SCALEKIT_CLIENT_ID,
process.env.SCALEKIT_CLIENT_SECRET
);
const redirectUri = 'http://localhost:3000/auth/callback';
Use the same redirectUri string that is registered in Step 1.
Done when: the client initializes from those env vars.
Step 3 — Login route
Pass one selector. Empty query fails.
app.get('/auth/login', (req, res) => {
const { connectionId, organizationId, loginHint, domainHint } = req.query;
const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, {
connectionId,
organizationId,
loginHint,
domainHint,
scopes: ['openid', 'profile', 'email'],
});
res.redirect(authorizationUrl);
});
Done when: /auth/login redirects to that URL with one selector.
Step 4 — IdP-initiated /login
Register this exact URL as Initiate login URL.
app.get('/login', async (req, res) => {
const { idp_initiated_login, error, error_description } = req.query;
if (error) return res.status(400).send(error_description);
const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login);
const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, {
connectionId: claims.connection_id,
organizationId: claims.organization_id,
loginHint: claims.login_hint,
state: claims.relay_state,
scopes: ['openid', 'profile', 'email'],
});
res.redirect(authorizationUrl);
});
Done when: /login reads idp_initiated_login and redirects with relay_state as state.
Step 5 — Callback and app session
Use the same redirectUri as Step 3. The app creates its own session after validation.
app.get('/auth/callback', async (req, res) => {
const { code, error, error_description } = req.query;
if (error) return res.status(400).send(error_description);
const result = await scalekit.authenticateWithCode(code, redirectUri);
await scalekit.validateToken(result.idToken);
await scalekit.validateToken(result.accessToken);
// Create the app's own session from result.user.
res.redirect('/dashboard');
});
Do not write SaaSKit HttpOnly cookies. Modular SSO does not return a refresh token.
Done when: the callback exchanges code, validates both tokens, and the app session exists.
Step 6 — Test with the simulator
Print this checklist. Wait for the user. Then continue.
- Dashboard → Organizations → Test Organization
- Copy the test
organization_idorconnection_id. Domains areexample.comandexample.org. - Open
/auth/loginwith one of:organizationId=<id>,connectionId=<id>, orloginHint=user@example.com - Complete the simulator (SP-initiated, then IdP-initiated)
Done when: the callback created the app session for a simulator login.
Step 7 — Admin portal
Shareable link (no code): Dashboard → Organizations → the org → Generate link. Send that URL plus the matching SSO setup guide to the customer's IT admin. Open references/providers.md for Okta, Entra ID, Google Workspace, and JumpCloud.
Embed: register the page origin under Authentication → Redirects → Allowed callback URLs. Generate the link server-side on every load. Links are single-use. orgId is the customer's organization ID from the dashboard.
const portalLink = await scalekit.organization.generatePortalLink(orgId);
res.json({ portalUrl: portalLink.location });
<iframe src="${portalUrl}" width="100%" height="600" frameborder="0" allow="clipboard-write"></iframe>
On message, if event.data.event_type is PORTAL_SESSION_EXPIRY, generate a new link and set iframe.src. Do the same for PORTAL_LOAD_FAILURE when data.error_code is SESSION_EXPIRED.
Done when: a shareable link can be generated, and the iframe uses a fresh portalLink.location.
Step 8 — Stop
Name implement-scim only if the user wants directory provisioning.
Done when: SAML/OIDC, IdP-initiated /login, and the admin portal are in the repo, and this skill has stopped.
Reach for
implement-saaskitif the app wants SaaSKit login insteadimplement-scimto provision users from a directory- references/providers.md for Okta, Entra ID, Google Workspace, and JumpCloud
Live lookups
- Docs index: https://docs.scalekit.com/llms.txt
- Modular SSO: https://docs.scalekit.com/authenticate/sso/add-modular-sso.md
- Admin portal: https://docs.scalekit.com/authenticate/sso/admin-portal/
- MCP: https://mcp.scalekit.com