Add Modular SSO
Add enterprise SSO to an app that keeps its own login: set up Scalekit, send a user to their identity provider and read their profile at your callback.
This quickstart adds enterprise single sign-on (SSO) to an app that keeps its own users and sessions. By the end, a test user signs in through the SSO simulator and your app reads their profile at its callback URL. New to Modular SSO? Start with What is Modular SSO.
Your app signs users in with Auth0, Amazon Cognito, Firebase or Supabase? Follow that system’s guide instead; see Choose how to integrate. Want Scalekit to run the whole login? Use the Full Stack Auth quickstart.
See a walkthrough of the integration
Using a coding agent?
Before you start
Section titled “Before you start”- A Scalekit account. Use your development environment: it comes with a test organization and the SSO simulator.
- A web app whose backend can run the Node.js, Python, Go or Java SDK.
-
Switch the environment to Modular Auth mode
Section titled “Switch the environment to Modular Auth mode”In the Scalekit dashboard, go to Settings > Authentication Mode, select Modular Auth and select Save. In this mode your app keeps its users and sessions, and Scalekit returns the verified user to your app.
-
Set up Scalekit
Section titled “Set up Scalekit”Install the SDK for your stack.
npm install @scalekit-sdk/nodepip install scalekit-sdk-pythongo get -u github.com/scalekit-inc/scalekit-sdk-go/v2The Java SDK is published to Scalekit’s Maven repository,
https://us-maven.pkg.dev/scalekit-public/java. Add it to your build next to Maven Central. It needs no credentials. If your builds download dependencies only through a mirror such as Nexus or Artifactory, add this URL to the mirror instead. For Maven, add the<repositories>and<dependency>entries to the matching sections of your existingpom.xml.// Gradle: build.gradlerepositories {mavenCentral()maven { url "https://us-maven.pkg.dev/scalekit-public/java" }}dependencies {implementation "com.scalekit:scalekit-sdk-java:2.6.1"}<!-- Maven: pom.xml --><repositories><repository><id>scalekit</id><url>https://us-maven.pkg.dev/scalekit-public/java</url></repository></repositories><dependencies><dependency><groupId>com.scalekit</groupId><artifactId>scalekit-sdk-java</artifactId><version>2.6.1</version></dependency></dependencies>Copy your API credentials from Developers > Settings > API Credentials into your
.envfile:.env SCALEKIT_ENVIRONMENT_URL=<your-environment-url> # Example: https://acme.scalekit.dev or https://auth.acme.com (if custom domain is set)SCALEKIT_CLIENT_ID=<app-client-id> # Example: skc_1234567890abcdefSCALEKIT_CLIENT_SECRET=<app-client-secret> # Example: test_abcdef1234567890 -
Register your callback URL
Section titled “Register your callback URL”Scalekit sends users back to your app only at URLs you register. Go to Authentication > Redirect URLs and add
http://localhost:3000/auth/callbackunder Allowed callback URLs. In production, add your app’s own callback URL. See Register redirect URLs. -
Send the user to their identity provider
Section titled “Send the user to their identity provider”Build an authorization URL with the SDK and redirect the user to it. The URL names your callback URL and the user’s organization. To try it now, use the ID of your test organization, under Organizations in the dashboard.
authorization-url.js import { ScalekitClient } from '@scalekit-sdk/node';const scalekit = new ScalekitClient('<SCALEKIT_ENVIRONMENT_URL>', // Your Scalekit environment URL'<SCALEKIT_CLIENT_ID>', // Unique identifier for your app'<SCALEKIT_CLIENT_SECRET>',);const options = {};// Send the user to their organization's SSO. To route by connection ID or by// the user's email domain instead, see /sso/guides/authorization-url/options['organizationId'] = 'org_15421144869927830';// redirect_uri: Your callback endpoint that receives the authorization code// Must match the URL registered in your Scalekit dashboardconst redirectUrl = 'http://localhost:3000/auth/callback';const authorizationURL = scalekit.getAuthorizationUrl(redirectUrl, options);// Redirect user to this URL to begin SSO authenticationauthorization_url.py from scalekit import ScalekitClient, AuthorizationUrlOptionsscalekit_client = ScalekitClient('<SCALEKIT_ENVIRONMENT_URL>', # Your Scalekit environment URL'<SCALEKIT_CLIENT_ID>', # Unique identifier for your app'<SCALEKIT_CLIENT_SECRET>')options = AuthorizationUrlOptions()# Send the user to their organization's SSO. To route by connection ID or by# the user's email domain instead, see /sso/guides/authorization-url/options.organization_id = 'org_15421144869927830'# redirect_uri: Your callback endpoint that receives the authorization code# Must match the URL registered in your Scalekit dashboardredirect_uri = 'http://localhost:3000/auth/callback'authorization_url = scalekit_client.get_authorization_url(redirect_uri=redirect_uri,options=options)# Redirect user to this URL to begin SSO authenticationauthorization_url.go package mainimport ("fmt""log"scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2")func main() {scalekitClient := scalekit.NewScalekitClient("<SCALEKIT_ENVIRONMENT_URL>", // Your Scalekit environment URL"<SCALEKIT_CLIENT_ID>", // Unique identifier for your app"<SCALEKIT_CLIENT_SECRET>",)options := scalekit.AuthorizationUrlOptions{}// Send the user to their organization's SSO. To route by connection ID or by// the user's email domain instead, see /sso/guides/authorization-url/options.OrganizationId = "org_15421144869927830"// redirectUrl: Your callback endpoint that receives the authorization code// Must match the URL registered in your Scalekit dashboardredirectUrl := "http://localhost:3000/auth/callback"authorizationURL, err := scalekitClient.GetAuthorizationUrl(redirectUrl, options)if err != nil {log.Fatal(err)}// Redirect user to this URL to begin SSO authenticationfmt.Println(authorizationURL.String())}AuthorizationUrl.java package com.scalekit;import com.scalekit.ScalekitClient;import com.scalekit.internal.http.AuthorizationUrlOptions;public class Main {public static void main(String[] args) {ScalekitClient scalekitClient = new ScalekitClient("<SCALEKIT_ENVIRONMENT_URL>", // Your Scalekit environment URL"<SCALEKIT_CLIENT_ID>", // Unique identifier for your app"<SCALEKIT_CLIENT_SECRET>");AuthorizationUrlOptions options = new AuthorizationUrlOptions();// Send the user to their organization's SSO. To route by connection ID or by// the user's email domain instead, see /sso/guides/authorization-url/options.setOrganizationId("org_13388706786312310");// redirectUrl: Your callback endpoint that receives the authorization code// Must match the URL registered in your Scalekit dashboardString redirectUrl = "http://localhost:3000/auth/callback";try {String url = scalekitClient.authentication().getAuthorizationUrl(redirectUrl, options).toString();// Redirect user to this URL to begin SSO authentication} catch (Exception e) {System.out.println(e.getMessage());}}}OAuth2 authorization URL <SCALEKIT_ENVIRONMENT_URL>/oauth/authorize?response_type=code& # OAuth2 authorization code flowclient_id=<SCALEKIT_CLIENT_ID>& # Your Scalekit client IDredirect_uri=<REDIRECT_URI>& # URL-encoded callback URLscope=openid profile email& # Modular SSO returns no refresh token, so leave out offline_accessorganization_id=org_15421144869927830 # Route to this organization's SSOTo route by connection ID or by the user’s email domain instead, see Authorization URL parameters.
Example with actual values https://tinotat-dev.scalekit.dev/oauth/authorize?response_type=code&client_id=skc_88036702639096097&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback&scope=openid%20profile%20email&organization_id=org_15421144869927830Scalekit sends the user to their organization’s identity provider. Once they sign in there, Scalekit sends them to your callback URL with a one-time code.
-
Read the user’s profile at your callback
Section titled “Read the user’s profile at your callback”Add an endpoint at your callback URL. It receives a
code, or anerroranderror_descriptionif the login failed. Exchange the code for the user’s profile:Fetch user profile // Extract authentication parameters from the callback requestconst { code, error, error_description } = req.query;if (error) {// The login failed at Scalekit or at the identity providerreturn res.status(401).send('Sign-in failed');}// The same callback URL you passed to getAuthorizationUrlconst redirectUri = 'http://localhost:3000/auth/callback';const result = await scalekit.authenticateWithCode(code, redirectUri);const userEmail = result.user.email;// Create a session for the authenticated user and grant appropriate access permissionsFetch user profile from scalekit import CodeAuthenticationOptions# Extract authentication parameters from the callback requestcode = request.args.get('code')error = request.args.get('error')error_description = request.args.get('error_description')if error:raise Exception(error_description)# The same callback URL you passed to get_authorization_urlredirect_uri = 'http://localhost:3000/auth/callback'result = scalekit_client.authenticate_with_code(code, redirect_uri, CodeAuthenticationOptions())# Access normalized user profile informationuser_email = result['user']['email']# Create a session for the authenticated user and grant appropriate access permissionsFetch user profile // Extract authentication parameters from the callback requestcode := r.URL.Query().Get("code")if errCode := r.URL.Query().Get("error"); errCode != "" {// The login failed at Scalekit or at the identity providerlog.Printf("SSO login failed: %s: %s", errCode, r.URL.Query().Get("error_description"))http.Error(w, "Sign-in failed", http.StatusUnauthorized)return}// The same callback URL you passed to GetAuthorizationUrlredirectUrl := "http://localhost:3000/auth/callback"result, err := scalekitClient.AuthenticateWithCode(r.Context(), code, redirectUrl, scalekit.AuthenticationOptions{})if err != nil {// The code exchange failedhttp.Error(w, "Sign-in failed", http.StatusUnauthorized)return}// Access normalized user profile informationlog.Printf("Signed in %s", result.User.Email)// Create a session for the authenticated user and grant appropriate access permissionsFetch user profile // Extract authentication parameters from the callback requestString code = request.getParameter("code");String error = request.getParameter("error");String errorDescription = request.getParameter("error_description");if (error != null && !error.isEmpty()) {// The login failed at Scalekit or at the identity providerreturn;}try {// The same callback URL you passed to getAuthorizationUrlString redirectUrl = "http://localhost:3000/auth/callback";AuthenticationResponse result = scalekitClient.authentication().authenticateWithCode(code, redirectUrl, new AuthenticationOptions());String userEmail = result.getIdTokenClaims().getEmail();// Create a session for the authenticated user and grant appropriate access permissions} catch (Exception e) {// Handle token exchange or validation errors}To read the claims in the tokens yourself, validate them with the SDK:
Validate tokens // Validate and decode the ID token from the authentication resultconst idTokenClaims = await scalekit.validateToken(result.idToken);// Validate and decode the access tokenconst accessTokenClaims = await scalekit.validateToken(result.accessToken);Validate tokens # Validate and decode the ID token from the authentication resultid_token_claims = scalekit_client.validate_token(result["id_token"])# Validate and decode the access tokenaccess_token_claims = scalekit_client.validate_token(result["access_token"])Validate tokens // Validate and decode the access token (uses JWKS from the client)accessTokenClaims, err := scalekitClient.GetAccessTokenClaims(r.Context(), result.AccessToken)if err != nil {// handle error}Validate tokens // The SDK validates the ID token and returns its claims in the resultIdTokenClaims idTokenClaims = result.getIdTokenClaims();// Validate and decode the access tokenMap<String, Object> accessTokenClaims = scalekitClient.authentication().validateAccessTokenAndGetClaims(result.getAccessToken());{user: {email: 'john@example.com',familyName: 'Doe',givenName: 'John',username: 'john@example.com',id: 'conn_70087756662964366;dcc62570-6a5a-4819-b11b-d33d110c7716'},idToken: 'eyJhbGciOiJSU..bcLQ',accessToken: 'eyJhbGciO..',expiresIn: 899}{amr: [ 'conn_70087756662964366' ], // SSO connection IDat_hash: 'yMGIBg7BkmIGgD6_dZPEGQ',aud: [ 'skc_70087756327420046' ],azp: 'skc_70087756327420046',c_hash: '4x7qsXnlRw6dRC6twnuENw',client_id: 'skc_70087756327420046',email: 'john@example.com',exp: 1758952038,family_name: 'Doe',given_name: 'John',iat: 1758692838,iss: '<SCALEKIT_ENVIRONMENT_URL>',oid: 'org_70087756646187150',preferred_username: 'john@example.com',sid: 'ses_91646612652163629',sub: 'conn_70087756662964366;e964d135-35c7-4a13-a3b4-2579a1cdf4e6'}{"iss": "<SCALEKIT_ENVIRONMENT_URL>","sub": "conn_70087756662964366;dcc62570-6a5a-4819-b11b-d33d110c7716","aud": ["skc_70087756327420046"],"exp": 1758693916,"iat": 1758693016,"nbf": 1758693016,"client_id": "skc_70087756327420046","jti": "tkn_91646913048216109"}user.idis the ID token’ssub: the connection ID and the user’s ID at the identity provider, joined by;. The ID token’soidclaim is the user’s organization ID. See Normalized user profile for every field.Before you go to production, check a
statevalue on each callback and find or create the user in your database. See Handle the callback and find the user. -
Check it worked
Section titled “Check it worked”- Open the authorization URL from step 4 with your test organization’s ID. The SSO simulator opens in place of a real identity provider.
- Leave User Login via SSO selected, enter a user name such as
jane(the domain isexample.com) and a first and last name, and select Simulate Login.
Your callback receives a
code, and the exchange returns a profile with the email and name you entered. If the callback receives anerrorinstead, look it up in the SSO error reference. For more test cases, such as IdP-initiated login and error scenarios, see Test SSO.