Quickstart Collection: All quickstart guides and getting started paths across all Scalekit products
---
# DOCUMENT BOUNDARY
---
# Scalekit Docs
> Auth, provider connections, and tool execution for AI agents and SaaS apps
# Choose your Scalekit documentation path [For Agent Builders](/agentkit/quickstart/) [Connect my agents to any enterprise app](/agentkit/quickstart/) [Delegated auth. Scoped permissions. Tool calls.](/agentkit/quickstart/) [Connect my agents to apps →](/agentkit/quickstart/) [](/agentkit/quickstart/) [For SaaS Developers](/authenticate/fsa/quickstart/) [Add auth and user management to my SaaS app](/authenticate/fsa/quickstart/) [Sessions, SSO, RBAC, SCIM - all in one stack.](/authenticate/fsa/quickstart/) [Add auth to my app →](/authenticate/fsa/quickstart/) [](/authenticate/fsa/quickstart/) ## Try it Run the CLI
```sh
npx @scalekit-inc/cli setup
```
* Claude Code * GitHub Copilot * Cursor * Codex or Quickstart with your agent Build with Scalekit.Load https\://docs.scalekit.com/llms.txt before writing any Scalekit code. The installed authstack skill is the source of truth for APIs, SDK calls, and connection names. Credentials live in the environment, never in source.Copy
---
# DOCUMENT BOUNDARY
---
# AgentKit: Connect my agent to apps
> Build a working agent that makes authenticated tool calls on behalf of users, using GitHub as the example connector.
 By the end of this guide, you’ll have a working agent that stars a repository on GitHub on behalf of a user (authenticated with their real account). Scalekit manages the OAuth flow, token storage, and API proxy so you focus on agent logic. ## Before you start [Section titled “Before you start”](#before-you-start) Complete these steps in the Scalekit dashboard before writing any code: 1. **Create a Scalekit account** at [app.scalekit.com](https://app.scalekit.com). 2. **Confirm the GitHub connection** at Dashboard → **AgentKit** > **Connections**. New Scalekit environments include a default GitHub connection named `github-connect`, pre-configured with Scalekit’s managed credentials and the `user:email`, `repo`, and `public_repo` scopes — no connector setup is needed for this quickstart. Copy the exact **Connection name** from that connection and use that value in your code. It must match the dashboard exactly; in older environments or renamed connections the value can differ from `github-connect`. To connect to other services, create a connection for each app under **AgentKit** > **Connections** > **Create Connection**. 3. **Copy your API credentials** at Dashboard → **Developers → Settings → API Credentials**. Save these values as environment variables: * `SCALEKIT_CLIENT_ID` * `SCALEKIT_CLIENT_SECRET` * `SCALEKIT_ENV_URL` * `GITHUB_CONNECTION_NAME` (copy the exact Connection name from **AgentKit** > **Connections** — `github-connect` in new environments) ## Build your agent [Section titled “Build your agent”](#build-your-agent) * Using a coding agent Install the authstack plugin for your coding agent with `npx @scalekit-inc/cli setup` (or install globally with `npm install -g @scalekit-inc/cli` then `scalekit setup`), complete the browser authorization when prompted, then paste the implementation prompt. The agent scaffolds connected account setup, the OAuth flow, and tool execution. Terminal
```bash
npx @scalekit-inc/cli setup
```
The wizard sets up the right plugins and skills for your editors. Complete any browser authorization for the Scalekit MCP server when prompted. Then use the prompt below (or describe your goal in natural language). Implementation prompt
```md
Configure Scalekit agent authentication for GitHub. Provide code to create a connected account, generate an authorization link, and, once the user authorizes, star Scalekit's SDK repo (scalekit-inc/scalekit-sdk-python) using Scalekit's tool API.
```
Review generated code before deploying Verify that token validation logic, error handling, and environment variable references match your application’s requirements. * Step by step ### 1. Set up your environment [Section titled “1. Set up your environment”](#1-set-up-your-environment) Install the Scalekit SDK and initialize the client with your API credentials: * Python
```sh
pip install scalekit-sdk-python python-dotenv
```
* Node.js
```sh
npm install @scalekit-sdk/node
```
- Python
```python
import os
from scalekit import ScalekitClient
from dotenv import load_dotenv
load_dotenv()
# Constructor: env_url, client_id, client_secret
scalekit_client = ScalekitClient(
os.environ["SCALEKIT_ENV_URL"],
os.environ["SCALEKIT_CLIENT_ID"],
os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
connection_name = os.getenv("GITHUB_CONNECTION_NAME") # must match the Connection name in the dashboard exactly
```
- Node.js
```typescript
import { ScalekitClient } from '@scalekit-sdk/node';
import { ConnectorStatus } from '@scalekit-sdk/node/lib/pkg/grpc/scalekit/v1/connected_accounts/connected_accounts_pb';
import 'dotenv/config';
// Constructor: envUrl, clientId, clientSecret
const scalekit = new ScalekitClient(
process.env.SCALEKIT_ENV_URL!,
process.env.SCALEKIT_CLIENT_ID!,
process.env.SCALEKIT_CLIENT_SECRET!
);
const actions = scalekit.actions;
const connectionName = process.env.GITHUB_CONNECTION_NAME!; // must match the Connection name in the dashboard exactly
```
### 2. Create a connected account [Section titled “2. Create a connected account”](#2-create-a-connected-account) Scalekit tracks each user’s third-party connection as a connected account. This is the record that holds their OAuth tokens. Creating it tells Scalekit to start managing the user’s GitHub access on your behalf. This step fails if the GitHub connection does not exist in **AgentKit** > **Connections**, or if `connection_name` / `connectionName` does not match the dashboard exactly. * Python
```python
# Create or retrieve the user's connected GitHub account
response = actions.get_or_create_connected_account(
connection_name=connection_name,
identifier="user_123" # Replace with your system's unique user ID
)
connected_account = response.connected_account
print(f'Connected account created: {connected_account.id}')
```
* Node.js
```typescript
// Create or retrieve the user's connected GitHub account
const response = await actions.getOrCreateConnectedAccount({
connectionName,
identifier: 'user_123', // Replace with your system's unique user ID
});
let connectedAccount = response.connectedAccount;
console.log('Connected account created:', connectedAccount?.id);
```
### 3. Authenticate the user [Section titled “3. Authenticate the user”](#3-authenticate-the-user) Your agent can’t act on behalf of a user until they authorize access. Generate an authorization link, send it to the user, and Scalekit handles the rest: token exchange, storage, and automatic refresh. Once they complete the flow, the connected account status becomes `ACTIVE`. * Python
```python
# Generate authorization link if user hasn't authorized or token is expired.
# Do not call tools until status is ACTIVE — wait for the user to finish OAuth first.
if connected_account.status != "ACTIVE":
print(f"GitHub is not connected: {connected_account.status}")
link_response = actions.get_authorization_link(
connection_name=connection_name,
identifier="user_123",
)
print("🔗 click on the link to authorize GitHub", link_response.link)
input("⎆ Press Enter after authorizing GitHub...")
# Re-fetch so connected_account reflects ACTIVE status and a valid id
response = actions.get_or_create_connected_account(
connection_name=connection_name,
identifier="user_123",
)
connected_account = response.connected_account
# In production, redirect the user to this URL and resume after the OAuth callback
if connected_account.status != "ACTIVE":
raise RuntimeError(
"GitHub is still not ACTIVE. Complete authorization and try again."
)
```
* Node.js
```typescript
// Generate authorization link if user hasn't authorized or token is expired.
// Do not call tools until status is ACTIVE — wait for the user to finish OAuth first.
if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
console.log('GitHub is not connected:', connectedAccount?.status);
const linkResponse = await actions.getAuthorizationLink({
connectionName,
identifier: 'user_123',
});
console.log('🔗 click on the link to authorize GitHub', linkResponse.link);
console.log('Press Enter after authorizing GitHub...');
await new Promise((resolve) => {
process.stdin.resume();
process.stdin.once('data', () => {
process.stdin.pause();
resolve();
});
});
// Re-fetch so connectedAccount reflects ACTIVE status and a valid id
const refreshed = await actions.getOrCreateConnectedAccount({
connectionName,
identifier: 'user_123',
});
connectedAccount = refreshed.connectedAccount;
// In production, redirect the user to this URL and resume after the OAuth callback
}
if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
throw new Error('GitHub is still not ACTIVE. Complete authorization and try again.');
}
```
Open the link in a browser and authorize the GitHub connection. Once complete, the connected account status updates to `ACTIVE` and your agent can act on the user’s behalf. In CLI samples, wait for that step (for example with `input()` or stdin) before calling tools — otherwise the first run fails because the account is still inactive. ### 4. Star a repo via tool call [Section titled “4. Star a repo via tool call”](#4-star-a-repo-via-tool-call) Pass the tool name and your inputs to Scalekit. It handles the request to GitHub and returns a structured response your agent can reason over directly: no endpoint URLs, auth headers, or response parsing required. The example stars the Scalekit SDK repo for your language. * Python
```python
# Prefer connected_account_id after authorize. If you use identifier instead,
# also pass connection_name — the pair is required for account resolution.
tool_response = actions.execute_tool(
tool_name="github_repo_star",
connected_account_id=connected_account.id,
tool_input={
"owner": "scalekit-inc",
"repo": "scalekit-sdk-python",
},
)
# Tool output lives under data
print(tool_response.data)
```
* Node.js
```typescript
const toolResponse = await actions.executeTool({
toolName: 'github_repo_star',
connectedAccountId: connectedAccount?.id,
toolInput: {
owner: 'scalekit-inc',
repo: 'scalekit-sdk-node',
},
});
// Tool output lives under data
console.log('Starred the repo:', toolResponse.data);
```
* Python
```sh
pip install scalekit-sdk-python python-dotenv
```
* Node.js
```sh
npm install @scalekit-sdk/node
```
* Python
```python
import os
from scalekit import ScalekitClient
from dotenv import load_dotenv
load_dotenv()
# Constructor: env_url, client_id, client_secret
scalekit_client = ScalekitClient(
os.environ["SCALEKIT_ENV_URL"],
os.environ["SCALEKIT_CLIENT_ID"],
os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
connection_name = os.getenv("GITHUB_CONNECTION_NAME") # must match the Connection name in the dashboard exactly
```
* Node.js
```typescript
import { ScalekitClient } from '@scalekit-sdk/node';
import { ConnectorStatus } from '@scalekit-sdk/node/lib/pkg/grpc/scalekit/v1/connected_accounts/connected_accounts_pb';
import 'dotenv/config';
// Constructor: envUrl, clientId, clientSecret
const scalekit = new ScalekitClient(
process.env.SCALEKIT_ENV_URL!,
process.env.SCALEKIT_CLIENT_ID!,
process.env.SCALEKIT_CLIENT_SECRET!
);
const actions = scalekit.actions;
const connectionName = process.env.GITHUB_CONNECTION_NAME!; // must match the Connection name in the dashboard exactly
```
* Python
```python
# Create or retrieve the user's connected GitHub account
response = actions.get_or_create_connected_account(
connection_name=connection_name,
identifier="user_123" # Replace with your system's unique user ID
)
connected_account = response.connected_account
print(f'Connected account created: {connected_account.id}')
```
* Node.js
```typescript
// Create or retrieve the user's connected GitHub account
const response = await actions.getOrCreateConnectedAccount({
connectionName,
identifier: 'user_123', // Replace with your system's unique user ID
});
let connectedAccount = response.connectedAccount;
console.log('Connected account created:', connectedAccount?.id);
```
* Python
```python
# Generate authorization link if user hasn't authorized or token is expired.
# Do not call tools until status is ACTIVE — wait for the user to finish OAuth first.
if connected_account.status != "ACTIVE":
print(f"GitHub is not connected: {connected_account.status}")
link_response = actions.get_authorization_link(
connection_name=connection_name,
identifier="user_123",
)
print("🔗 click on the link to authorize GitHub", link_response.link)
input("⎆ Press Enter after authorizing GitHub...")
# Re-fetch so connected_account reflects ACTIVE status and a valid id
response = actions.get_or_create_connected_account(
connection_name=connection_name,
identifier="user_123",
)
connected_account = response.connected_account
# In production, redirect the user to this URL and resume after the OAuth callback
if connected_account.status != "ACTIVE":
raise RuntimeError(
"GitHub is still not ACTIVE. Complete authorization and try again."
)
```
* Node.js
```typescript
// Generate authorization link if user hasn't authorized or token is expired.
// Do not call tools until status is ACTIVE — wait for the user to finish OAuth first.
if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
console.log('GitHub is not connected:', connectedAccount?.status);
const linkResponse = await actions.getAuthorizationLink({
connectionName,
identifier: 'user_123',
});
console.log('🔗 click on the link to authorize GitHub', linkResponse.link);
console.log('Press Enter after authorizing GitHub...');
await new Promise((resolve) => {
process.stdin.resume();
process.stdin.once('data', () => {
process.stdin.pause();
resolve();
});
});
// Re-fetch so connectedAccount reflects ACTIVE status and a valid id
const refreshed = await actions.getOrCreateConnectedAccount({
connectionName,
identifier: 'user_123',
});
connectedAccount = refreshed.connectedAccount;
// In production, redirect the user to this URL and resume after the OAuth callback
}
if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
throw new Error('GitHub is still not ACTIVE. Complete authorization and try again.');
}
```
* Python
```python
# Prefer connected_account_id after authorize. If you use identifier instead,
# also pass connection_name — the pair is required for account resolution.
tool_response = actions.execute_tool(
tool_name="github_repo_star",
connected_account_id=connected_account.id,
tool_input={
"owner": "scalekit-inc",
"repo": "scalekit-sdk-python",
},
)
# Tool output lives under data
print(tool_response.data)
```
* Node.js
```typescript
const toolResponse = await actions.executeTool({
toolName: 'github_repo_star',
connectedAccountId: connectedAccount?.id,
toolInput: {
owner: 'scalekit-inc',
repo: 'scalekit-sdk-node',
},
});
// Tool output lives under data
console.log('Starred the repo:', toolResponse.data);
```
## Verify it works [Section titled “Verify it works”](#verify-it-works) Run your agent and confirm: * The connected account status is `ACTIVE` after the user completes the GitHub OAuth flow. * The tool call returns success and the star appears on the Scalekit SDK repo on GitHub. Starring is idempotent — GitHub returns success even if the repo is already starred — so re-runs are safe. To confirm programmatically, call `github_starred_repos_list` and check the repo is in the list. If the connected account stays in a `non-ACTIVE` state, the user has not completed the OAuth flow. Regenerate the authorization link and try again. ## Next steps [Section titled “Next steps”](#next-steps) * [Secure user verification](/agentkit/user-verification/): Confirm the OAuth identity matches your logged-in user before activating a connected account. Required for production. * [Connected accounts](/agentkit/connected-accounts/): Manage user connections across multiple providers. * [Tool calling](/agentkit/tools/scalekit-optimized-tools/): Use Scalekit’s optimized tools to call APIs without managing endpoints yourself.
---
# DOCUMENT BOUNDARY
---
# SaaSKit: Add auth to my app
> SaaSKit — Hosted auth pages, managed sessions, secure logout. Purpose built. Simple where it counts
You’ll implement sign-up, login, and logout flows with secure session management and user management included. The foundation you build here extends to features like workspaces, enterprise SSO, MCP authentication, and SCIM provisioning. ### Build with a coding agent * Global install (recommended) Terminal
```bash
npm install -g @scalekit-inc/cli
scalekit setup
```
* npx (one-off) Terminal
```bash
npx @scalekit-inc/cli setup
```
*** 1. ## Set up Scalekit [Section titled “Set up Scalekit”](#set-up-scalekit) Use the following instructions to install the SDK for your technology stack. * Node.js
```bash
npm install @scalekit-sdk/node
```
* Python
```sh
pip install scalekit-sdk-python
```
* Go
```sh
go get -u github.com/scalekit-inc/scalekit-sdk-go
```
* Java
```groovy
/* Gradle users - add the following to your dependencies in build file */
implementation "com.scalekit:scalekit-sdk-java:2.1.3"
```
```xml
com.scalekit
scalekit-sdk-java
2.1.3
```
If you haven’t already, add your Scalekit credentials to your environment variables file: .env
```sh
SCALEKIT_ENVIRONMENT_URL=
SCALEKIT_CLIENT_ID=
SCALEKIT_CLIENT_SECRET=
```
### Register redirect URLs for your app [Section titled “Register redirect URLs for your app”](#register-redirect-urls-for-your-app) You need to register redirect URLs for your application. Go to **Scalekit dashboard** → **Authentication** → **Redirect URLs** and configure: * **Allowed callback URLs**: The endpoint where users are sent after successful authentication to exchange authorization codes and retrieve profile information. [Learn more](/guides/dashboard/redirects/#allowed-callback-urls) * **Initiate login URL**: The endpoint in your app that redirects users to Scalekit’s `/authorize` endpoint. Required when authentication is not initiated from your app, for example, when a user accepts an organization invitation or starts sign-in directly from their identity provider (IdP-initiated SSO). [Learn more](/guides/dashboard/redirects/#initiate-login-url) 2. ## Redirect users to sign up (or) login [Section titled “Redirect users to sign up (or) login”](#redirect-users-to-sign-up-or-login) An authorization URL is an endpoint that redirects users to Scalekit’s sign-in page. Use the Scalekit SDK to construct this URL with your redirect URI and required scopes. * Node.js routes/auth.ts
```javascript
1
// Must match the allowed callback URL you registered in the dashboard
2
const redirectUri = 'http://localhost:3000/auth/callback';
3
4
// Request user profile data (openid, profile, email) and session tracking (offline_access)
5
// offline_access enables refresh tokens so users can stay logged in across sessions
6
const options = {
7
scopes: ['openid', 'profile', 'email', 'offline_access']
8
};
9
10
const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options);
11
// Generated URL will look like:
12
// https:///oauth/authorize?response_type=code&client_id=skc_1234&scope=openid%20profile%20email%20offline_access&redirect_uri=https%3A%2F%2Fyourapp.com%2Fauth%2Fcallback
13
14
res.redirect(authorizationUrl);
```
* Python app/auth/routes.py
```python
1
from scalekit import AuthorizationUrlOptions
2
3
# Must match the allowed callback URL you registered in the dashboard
4
redirect_uri = 'http://localhost:3000/auth/callback'
5
6
# Request user profile data (openid, profile, email) and session tracking (offline_access)
7
# offline_access enables refresh tokens so users can stay logged in across sessions
8
options = AuthorizationUrlOptions()
9
options.scopes = ['openid', 'profile', 'email', 'offline_access']
10
11
12
authorization_url = scalekit.get_authorization_url(redirect_uri, options)
13
# Generated URL will look like:
14
# https:///oauth/authorize?response_type=code&client_id=skc_1234&scope=openid%20profile%20email%20offline_access&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback
15
16
return redirect(authorization_url)
```
* Go internal/http/auth.go
```go
1
// Must match the allowed callback URL you registered in the dashboard
2
redirectUri := "http://localhost:3000/auth/callback"
3
4
// Request user profile data (openid, profile, email) and session tracking (offline_access)
5
// offline_access enables refresh tokens so users can stay logged in across sessions
6
options := scalekit.AuthorizationUrlOptions{
7
Scopes: []string{"openid", "profile", "email", "offline_access"}
8
}
9
10
authorizationUrl, err := scalekitClient.GetAuthorizationUrl(redirectUri, options)
11
// Generated URL will look like:
12
// https:///oauth/authorize?response_type=code&client_id=skc_1234&scope=openid%20profile%20email%20offline_access&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback
13
if err != nil {
14
// Handle error based on your application's error handling strategy
15
panic(err)
16
}
17
18
c.Redirect(http.StatusFound, authorizationUrl.String())
```
* Java AuthController.java
```java
1
import com.scalekit.internal.http.AuthorizationUrlOptions;
2
import java.net.URL;
3
import java.util.Arrays;
4
5
// Must match the allowed callback URL you registered in the dashboard
6
String redirectUri = "http://localhost:3000/auth/callback";
7
8
// Request user profile data (openid, profile, email) and session tracking (offline_access)
9
// offline_access enables refresh tokens so users can stay logged in across sessions
10
AuthorizationUrlOptions options = new AuthorizationUrlOptions();
11
options.setScopes(Arrays.asList("openid", "profile", "email", "offline_access"));
12
13
URL authorizationUrl = scalekit.authentication().getAuthorizationUrl(redirectUri, options);
14
// Generated URL will look like:
15
// https:///oauth/authorize?response_type=code&client_id=skc_1234&scope=openid%20profile%20email%20offline_access&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback
```
This redirects users to Scalekit’s managed sign-in page where they can authenticate. The page includes default authentication methods for users to toggle between sign in and sign up. 3. ## Get user details from the callback [Section titled “Get user details from the callback”](#get-user-details-from-the-callback) After successful authentication, Scalekit creates a user record and sends the user information to your callback endpoint. In authentication flow, Scalekit redirects to your callback URL with an authorization code. Your application exchanges this code for the user’s profile information and session tokens. * Node.js routes/auth-callback.ts
```javascript
1
import scalekit from '@/utils/auth.js'
2
const redirectUri = '';
3
4
// Get the authorization code from the scalekit initiated callback
5
app.get('/auth/callback', async (req, res) => {
6
const { code, error, error_description } = req.query;
7
8
if (error) {
9
return res.status(401).json({ error, error_description });
10
}
11
12
try {
13
// Exchange the authorization code for user profile and session tokens
14
// Returns: user (profile info), idToken (JWT with user claims), accessToken (JWT with roles/permissions), refreshToken
15
const authResult = await scalekit.authenticateWithCode(
16
code, redirectUri
17
);
18
19
const { user, idToken, accessToken, refreshToken } = authResult;
20
// idToken: Decode to access full user profile (sub, oid, email, name)
21
// accessToken: Contains roles and permissions for authorization decisions
22
// refreshToken: Use to obtain new access tokens when they expire
23
24
// "user" object contains the user's profile information
25
// Next step: Create a session and log in the user
26
res.redirect('/dashboard/profile');
27
} catch (err) {
28
console.error('Error exchanging code:', err);
29
res.status(500).json({ error: 'Failed to authenticate user' });
30
}
31
});
```
* Python app/auth/callback.py
```python
1
from flask import Flask, request, redirect, jsonify
2
from scalekit import ScalekitClient, CodeAuthenticationOptions
3
4
app = Flask(__name__)
5
# scalekit imported from your auth utils
6
7
redirect_uri = 'http://localhost:3000/auth/callback'
8
9
@app.route('/auth/callback')
10
def callback():
11
code = request.args.get('code')
12
error = request.args.get('error')
13
error_description = request.args.get('error_description')
14
15
if error:
16
return jsonify({'error': error, 'error_description': error_description}), 401
17
18
try:
19
# Exchange the authorization code for user profile and session tokens
20
# Returns: user (profile info), id_token (JWT with user claims), access_token (JWT with roles/permissions), refresh_token
21
options = CodeAuthenticationOptions()
22
auth_result = scalekit.authenticate_with_code(
23
code, redirect_uri, options
24
)
25
26
user = auth_result["user"]
27
# id_token: Decode to access full user profile (sub, oid, email, name)
28
# access_token: Contains roles and permissions for authorization decisions
29
# refresh_token: Use to obtain new access tokens when they expire
30
31
# "user" object contains the user's profile information
32
# Next step: Create a session and log in the user
33
return redirect('/dashboard/profile')
34
except Exception as err:
35
print(f'Error exchanging code: {err}')
36
return jsonify({'error': 'Failed to authenticate user'}), 500
```
* Go internal/http/auth\_callback.go
```go
1
package main
2
3
import (
4
"log"
5
"net/http"
6
"os"
7
"github.com/gin-gonic/gin"
8
"github.com/scalekit-inc/scalekit-sdk-go"
9
)
10
11
// Create Scalekit client instance
12
var scalekitClient = scalekit.NewScalekitClient(
13
os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
14
os.Getenv("SCALEKIT_CLIENT_ID"),
15
os.Getenv("SCALEKIT_CLIENT_SECRET"),
16
)
17
18
const redirectUri = "http://localhost:3000/auth/callback"
19
20
func callbackHandler(c *gin.Context) {
21
code := c.Query("code")
22
errorParam := c.Query("error")
23
errorDescription := c.Query("error_description")
24
25
if errorParam != "" {
26
c.JSON(http.StatusUnauthorized, gin.H{
27
"error": errorParam,
28
"error_description": errorDescription,
29
})
30
return
31
}
32
33
// Exchange the authorization code for user profile and session tokens
34
// Returns: User (profile info), IdToken (JWT with user claims), AccessToken (JWT with roles/permissions), RefreshToken
35
options := scalekit.AuthenticationOptions{}
36
authResult, err := scalekitClient.AuthenticateWithCode(
37
c.Request.Context(), code, redirectUri, options,
38
)
39
40
if err != nil {
41
log.Printf("Error exchanging code: %v", err)
42
c.JSON(http.StatusInternalServerError, gin.H{
43
"error": "Failed to authenticate user",
44
})
45
return
46
}
47
48
user := authResult.User
49
// IdToken: Decode to access full user profile (sub, oid, email, name)
50
// AccessToken: Contains roles and permissions for authorization decisions
51
// RefreshToken: Use to obtain new access tokens when they expire
52
53
// "user" object contains the user's profile information
54
// Next step: Create a session and log in the user
55
c.Redirect(http.StatusFound, "/dashboard/profile")
56
}
```
* Java CallbackController.java
```java
1
import com.scalekit.ScalekitClient;
2
import com.scalekit.internal.http.AuthenticationOptions;
3
import com.scalekit.internal.http.AuthenticationResponse;
4
import org.springframework.web.bind.annotation.*;
5
import org.springframework.web.servlet.view.RedirectView;
6
import org.springframework.http.ResponseEntity;
7
import org.springframework.http.HttpStatus;
8
import java.util.HashMap;
9
import java.util.Map;
10
11
@RestController
12
public class CallbackController {
13
14
private final String redirectUri = "http://localhost:3000/auth/callback";
15
16
@GetMapping("/auth/callback")
17
public Object callback(
18
@RequestParam(required = false) String code,
19
@RequestParam(required = false) String error,
20
@RequestParam(name = "error_description", required = false) String errorDescription
21
) {
22
if (error != null) {
23
// handle error
24
}
25
26
try {
27
// Exchange the authorization code for user profile and session tokens
28
// Returns: user (profile info), idToken (JWT with user claims), accessToken (JWT with roles/permissions), refreshToken
29
AuthenticationOptions options = new AuthenticationOptions();
30
AuthenticationResponse authResult = scalekit
31
.authentication()
32
.authenticateWithCode(code,redirectUri,options);
33
34
var user = authResult.getIdTokenClaims();
35
// idToken: Decode to access full user profile (sub, oid, email, name)
36
// accessToken: Contains roles and permissions for authorization decisions
37
// refreshToken: Use to obtain new access tokens when they expire
38
39
// "user" object contains the user's profile information
40
// Next step: Create a session and log in the user
41
return new RedirectView("/dashboard/profile");
42
43
} catch (Exception err) {
44
// Handle exception (e.g., log error, return error response)
45
}
46
}
47
}
```
The `authResult` object contains: * `user` - Common user details with email, name, and verification status * `idToken` - JWT containing verified full user identity claims (includes: `sub` user ID, `oid` organization ID, `email`, `name`, `exp` expiration) * `accessToken` - Short-lived token that determines current access context (includes: `sub` user ID, `oid` organization ID, `roles`, `permissions`, `exp` expiration) * `refreshToken` - Long-lived token to obtain new access tokens - Auth result
```js
1
{
2
user: {
3
email: "john.doe@example.com",
4
emailVerified: true,
5
givenName: "John",
6
name: "John Doe",
7
id: "usr_74599896446906854"
8
},
9
idToken: "eyJhbGciO..", // Decode for full user details
10
11
accessToken: "eyJhbGciOi..",
12
refreshToken: "rt_8f7d6e5c4b3a2d1e0f9g8h7i6j..",
13
expiresIn: 299 // in seconds
14
}
```
- Decoded ID token ID token decoded
```json
1
{
2
"at_hash": "ec_jU2ZKpFelCKLTRWiRsg",
3
"aud": [
4
"skc_58327482062864390"
5
],
6
"azp": "skc_58327482062864390",
7
"c_hash": "6wMreK9kWQQY6O5R0CiiYg",
8
"client_id": "skc_58327482062864390",
9
"email": "john.doe@example.com",
10
"email_verified": true,
11
"exp": 1742975822,
12
"family_name": "Doe",
13
"given_name": "John",
14
"iat": 1742974022,
15
"iss": "https://scalekit-z44iroqaaada-dev.scalekit.cloud",
16
"name": "John Doe",
17
"oid": "org_59615193906282635",
18
"sid": "ses_65274187031249433",
19
"sub": "usr_63261014140912135"
20
}
```
- Decoded access token Decoded access token
```json
1
{
2
"aud": [
3
"prd_skc_7848964512134X699"
4
],
5
"client_id": "prd_skc_7848964512134X699",
6
"exp": 1758265247,
7
"iat": 1758264947,
8
"iss": "https://login.devramp.ai",
9
"jti": "tkn_90928731115292X63",
10
"nbf": 1758264947,
11
"oid": "org_89678001X21929734",
12
"permissions": [
13
"workspace_data:write",
14
"workspace_data:read"
15
],
16
"roles": [
17
"admin"
18
],
19
"sid": "ses_90928729571723X24",
20
"sub": "usr_8967800122X995270",
21
// External identifiers if updated on Scalekit
22
"xoid": "ext_org_123", // Organization ID
23
"xuid": "ext_usr_456", // User ID
24
}
```
The user details are packaged in the form of JWT tokens. Decode the `idToken` to access full user profile information (email, name, organization ID) and the `accessToken` to check user roles and permissions for authorization decisions. See [Complete login with code exchange](/authenticate/fsa/complete-login/) for detailed token claim references and verification instructions. 4. ## Create and manage user sessions [Section titled “Create and manage user sessions”](#create-and-manage-user-sessions) The access token is a JWT that contains the user’s permissions and roles. It expires in 5 minutes (default) but [can be configured](/authenticate/fsa/manage-session/#configure-session-security-and-duration). When it expires, use the refresh token to obtain a new access token. The refresh token is long-lived and designed for this purpose. The Scalekit SDK provides methods to refresh access tokens automatically. However, you must log the user out when the refresh token itself expires or becomes invalid. * Node.js
```javascript
1
import cookieParser from 'cookie-parser';
2
// Set cookie parser middleware
3
app.use(cookieParser());
4
5
// Store access token in HttpOnly cookie with Path scoping to API routes
6
res.cookie('accessToken', authResult.accessToken, {
7
maxAge: (authResult.expiresIn - 60) * 1000,
8
httpOnly: true,
9
secure: true,
10
path: '/api',
11
sameSite: 'strict'
12
});
13
14
// Store refresh token in separate HttpOnly cookie with Path scoped to refresh endpoint
15
res.cookie('refreshToken', authResult.refreshToken, {
16
httpOnly: true,
17
secure: true,
18
path: '/auth/refresh',
19
sameSite: 'strict'
20
});
```
* Python
```python
1
from flask import Flask, make_response
2
import os
3
4
# Cookie parsing is built-in with Flask's request object
5
app = Flask(__name__)
6
7
response = make_response()
8
9
# Store access token in HttpOnly cookie with Path scoping to API routes
10
response.set_cookie(
11
'accessToken',
12
auth_result.access_token,
13
max_age=auth_result.expires_in - 60, # seconds in Flask
14
httponly=True,
15
secure=True,
16
path='/api',
17
samesite='Strict'
18
)
19
20
# Store refresh token in separate HttpOnly cookie with Path scoped to refresh endpoint
21
response.set_cookie(
22
'refreshToken',
23
auth_result.refresh_token,
24
httponly=True,
25
secure=True,
26
path='/auth/refresh',
27
samesite='Strict'
28
)
```
* Go
```go
1
import (
2
"net/http"
3
"os"
4
)
5
6
// Set SameSite mode for CSRF protection
7
c.SetSameSite(http.SameSiteStrictMode)
8
9
// Store access token in HttpOnly cookie with Path scoping to API routes
10
c.SetCookie(
11
"accessToken",
12
authResult.AccessToken,
13
authResult.ExpiresIn-60, // seconds in Gin
14
"/api",
15
"",
16
os.Getenv("GIN_MODE") == "release",
17
true,
18
)
19
20
// Store refresh token in separate HttpOnly cookie with Path scoped to refresh endpoint
21
c.SetCookie(
22
"refreshToken",
23
authResult.RefreshToken,
24
0, // No expiry for refresh token cookie
25
"/auth/refresh",
26
"",
27
os.Getenv("GIN_MODE") == "release",
28
true,
29
)
```
* Java
```java
1
import javax.servlet.http.Cookie;
2
import javax.servlet.http.HttpServletResponse;
3
4
// Store access token in HttpOnly cookie with Path scoping to API routes
5
Cookie accessTokenCookie = new Cookie("accessToken", authResult.getAccessToken());
6
accessTokenCookie.setMaxAge(authResult.getExpiresIn() - 60); // seconds in Spring
7
accessTokenCookie.setHttpOnly(true);
8
accessTokenCookie.setSecure(true);
9
accessTokenCookie.setPath("/api");
10
response.addCookie(accessTokenCookie);
11
12
// Store refresh token in separate HttpOnly cookie with Path scoped to refresh endpoint
13
Cookie refreshTokenCookie = new Cookie("refreshToken", authResult.getRefreshToken());
14
refreshTokenCookie.setHttpOnly(true);
15
refreshTokenCookie.setSecure(true);
16
refreshTokenCookie.setPath("/auth/refresh");
17
response.addCookie(refreshTokenCookie);
18
response.setHeader("Set-Cookie",
19
response.getHeader("Set-Cookie") + "; SameSite=Strict");
```
This sets browser cookies with the session tokens. Every request to your backend needs to verify the `accessToken` to ensure the user is authenticated. If expired, use the `refreshToken` to get a new access token. * Node.js
```javascript
1
// Middleware to verify and refresh tokens if needed
2
const verifyToken = async (req, res, next) => {
3
try {
4
// Get access token from cookie and decrypt it
5
const accessToken = req.cookies.accessToken;
6
const decryptedAccessToken = decrypt(accessToken);
7
8
if (!accessToken) {
9
return res.status(401).json({ message: 'No access token provided' });
10
}
11
12
// Use Scalekit SDK to validate the token
13
const isValid = await scalekit.validateAccessToken(decryptedAccessToken);
14
15
if (!isValid) {
16
// Use stored refreshToken to get a new access token
17
const {
18
user,
19
idToken,
20
accessToken,
21
refreshToken: newRefreshToken,
22
} = await scalekit.refreshAccessToken(refreshToken);
23
24
// Store the new refresh token
25
// Update the cookie with the new access token
26
}
27
next();
28
};
29
30
// Example of using the middleware to protect routes
31
app.get('/dashboard', verifyToken, (req, res) => {
32
// The user object is now available in req.user
33
res.json({
34
message: 'This is a protected route',
35
user: req.user
36
});
37
});
```
* Python
```python
1
from functools import wraps
2
from flask import request, jsonify, make_response
3
4
def verify_token(f):
5
"""Decorator to verify and refresh tokens if needed"""
6
@wraps(f)
7
def decorated_function(*args, **kwargs):
8
try:
9
# Get access token from cookie
10
access_token = request.cookies.get('accessToken')
11
12
if not access_token:
13
return jsonify({'message': 'No access token provided'}), 401
14
15
# Decrypt the accessToken using the same encryption algorithm
16
decrypted_access_token = decrypt(access_token)
17
18
# Use Scalekit SDK to validate the token
19
is_valid = scalekit.validate_access_token(decrypted_access_token)
20
21
if not is_valid:
22
# Get stored refresh token
23
refresh_token = get_stored_refresh_token()
24
25
if not refresh_token:
26
return jsonify({'message': 'No refresh token available'}), 401
27
28
# Use stored refreshToken to get a new access token
29
token_response = scalekit.refresh_access_token(refresh_token)
30
31
# Python SDK returns dict with access_token and refresh_token
32
new_access_token = token_response.get('access_token')
33
new_refresh_token = token_response.get('refresh_token')
34
35
# Store the new refresh token
36
store_refresh_token(new_refresh_token)
37
38
# Update the cookie with the new access token
39
encrypted_new_access_token = encrypt(new_access_token)
40
response = make_response(f(*args, **kwargs))
41
response.set_cookie(
42
'accessToken',
43
encrypted_new_access_token,
44
httponly=True,
45
secure=True,
46
path='/',
47
samesite='Strict'
48
)
49
50
return response
51
52
# If the token was valid we just invoke the view as-is
53
return f(*args, **kwargs)
54
55
except Exception as e:
56
return jsonify({'message': f'Token verification failed: {str(e)}'}), 401
57
58
return decorated_function
59
60
# Example of using the decorator to protect routes
61
@app.route('/dashboard')
62
@verify_token
63
def dashboard():
64
return jsonify({
65
'message': 'This is a protected route',
66
'user': getattr(request, 'user', None)
67
})
```
* Go
```go
1
import (
2
"context"
3
"net/http"
4
)
5
6
// verifyToken is a middleware that ensures a valid access token or refreshes it if expired.
7
func verifyToken(next http.HandlerFunc) http.HandlerFunc {
8
return func(w http.ResponseWriter, r *http.Request) {
9
// Retrieve the access token from the user's cookie
10
cookie, err := r.Cookie("accessToken")
11
if err != nil {
12
// No access token cookie found; reject the request
13
http.Error(w, `{"message": "No access token provided"}`, http.StatusUnauthorized)
14
return
15
}
16
17
accessToken := cookie.Value
18
19
// Decrypt the access token before validation
20
decryptedAccessToken, err := decrypt(accessToken)
21
if err != nil {
22
// Could not decrypt access token; treat as invalid
23
http.Error(w, `{"message": "Token decryption failed"}`, http.StatusUnauthorized)
24
return
25
}
26
27
// Validate the access token using the Scalekit SDK
28
isValid, err := scalekitClient.ValidateAccessToken(r.Context(), decryptedAccessToken)
29
if err != nil || !isValid {
30
// Access token is invalid or expired
31
32
// Attempt to retrieve the stored refresh token
33
refreshToken, err := getStoredRefreshToken(r)
34
if err != nil {
35
// No refresh token is available; cannot continue
36
http.Error(w, `{"message": "No refresh token available"}`, http.StatusUnauthorized)
37
return
38
}
39
40
// Use the refresh token to obtain a new access token from Scalekit
41
tokenResponse, err := scalekitClient.RefreshAccessToken(r.Context(), refreshToken)
42
if err != nil {
43
// Refresh attempt failed; likely an expired or invalid refresh token
44
http.Error(w, `{"message": "Token refresh failed"}`, http.StatusUnauthorized)
45
return
46
}
47
48
// Save the new refresh token so it can be reused for future requests
49
err = storeRefreshToken(tokenResponse.RefreshToken)
50
if err != nil {
51
// Could not store the new refresh token
52
http.Error(w, `{"message": "Failed to store refresh token"}`, http.StatusInternalServerError)
53
return
54
}
55
56
// Encrypt the new access token before setting it in the cookie
57
encryptedNewAccessToken, err := encrypt(tokenResponse.AccessToken)
58
if err != nil {
59
// Could not encrypt new access token
60
http.Error(w, `{"message": "Token encryption failed"}`, http.StatusInternalServerError)
61
return
62
}
63
64
// Issue a new accessToken cookie with updated credentials
65
newCookie := &http.Cookie{
66
Name: "accessToken",
67
Value: encryptedNewAccessToken,
68
HttpOnly: true,
69
Secure: true,
70
Path: "/",
71
SameSite: http.SameSiteStrictMode,
72
}
73
http.SetCookie(w, newCookie)
74
75
// Mark the token as valid in the request context and proceed
76
r = r.WithContext(context.WithValue(r.Context(), "tokenValid", true))
77
} else {
78
// The access token is valid; continue with marked context
79
r = r.WithContext(context.WithValue(r.Context(), "tokenValid", true))
80
}
81
82
// Pass the request along to the next handler in the chain
83
next(w, r)
84
}
85
}
86
87
// dashboardHandler demonstrates a protected route that requires authentication.
88
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
89
w.Header().Set("Content-Type", "application/json")
90
w.Write([]byte(`{
91
"message": "This is a protected route",
92
"tokenValid": true
93
}`))
94
}
95
96
// Usage example:
97
// Attach middleware to the /dashboard route:
98
// http.HandleFunc("/dashboard", verifyToken(dashboardHandler))
```
* Java
```java
1
import javax.servlet.http.HttpServletRequest;
2
import javax.servlet.http.HttpServletResponse;
3
import javax.servlet.http.Cookie;
4
import org.springframework.web.servlet.HandlerInterceptor;
5
6
@Component
7
public class TokenVerificationInterceptor implements HandlerInterceptor {
8
@Override
9
public boolean preHandle(
10
HttpServletRequest request,
11
HttpServletResponse response,
12
Object handler
13
) throws Exception {
14
try {
15
// Get access token from cookie
16
String accessToken = getCookieValue(request, "accessToken");
17
String refreshToken = getCookieValue(request, "refreshToken");
18
19
// Decrypt the tokens
20
String decryptedAccessToken = decrypt(accessToken);
21
String decryptedRefreshToken = decrypt(refreshToken);
22
23
// Use Scalekit SDK to validate the token
24
boolean isValid = scalekit.authentication().validateAccessToken(decryptedAccessToken);
25
26
27
// Use refreshToken to get a new access token
28
AuthenticationResponse tokenResponse = scalekit
29
.authentication()
30
.refreshToken(decryptedRefreshToken);
31
32
// Update the cookie with the new access token and refresh token
33
String encryptedNewAccessToken = encrypt(tokenResponse.getAccessToken());
34
String encryptedNewRefreshToken = encrypt(tokenResponse.getRefreshToken());
35
36
Cookie accessTokenCookie = new Cookie("accessToken", encryptedNewAccessToken);
37
accessTokenCookie.setHttpOnly(true);
38
accessTokenCookie.setSecure(true);
39
accessTokenCookie.setPath("/");
40
response.addCookie(accessTokenCookie);
41
42
Cookie refreshTokenCookie = new Cookie("refreshToken", encryptedNewRefreshToken);
43
refreshTokenCookie.setHttpOnly(true);
44
refreshTokenCookie.setSecure(true);
45
refreshTokenCookie.setPath("/");
46
response.addCookie(refreshTokenCookie);
47
48
return true;
49
} catch (Exception e) {
50
// handle exception
51
}
52
}
53
54
private String getCookieValue(HttpServletRequest request, String cookieName) {
55
Cookie[] cookies = request.getCookies();
56
if (cookies != null) {
57
for (Cookie cookie : cookies) {
58
if (cookieName.equals(cookie.getName())) {
59
return cookie.getValue();
60
}
61
}
62
}
63
return null;
64
}
65
}
```
Authenticated users can access your dashboard. The app enforces session policies using session tokens. To change session policies, go to Dashboard > Authentication > Session Policy in the Scalekit dashboard. 5. ## Log out the user [Section titled “Log out the user”](#log-out-the-user) Session persistence depends on the session policy configured in the Scalekit dashboard. To log out a user, clear local session data and invalidate the user’s session in Scalekit. * Node.js
```javascript
1
app.get('/logout', (req, res) => {
2
// Clear all session data including cookies and local storage
3
clearSessionData();
4
5
const logoutUrl = scalekit.getLogoutUrl(
6
idTokenHint, // ID token to invalidate
7
postLogoutRedirectUri // URL that scalekit redirects after session invalidation
8
);
9
10
// Redirect the user to the Scalekit logout endpoint to begin invalidating the session.
11
res.redirect(logoutUrl); // This URL can only be used once and expires after logout.
12
});
```
* Python
```python
1
from flask import Flask, redirect
2
from scalekit.common.scalekit import LogoutUrlOptions
3
4
app = Flask(__name__)
5
6
@app.route('/logout')
7
def logout():
8
# Clear all session data including cookies and local storage
9
clear_session_data()
10
11
# Generate Scalekit logout URL
12
options = LogoutUrlOptions(
13
id_token_hint=id_token,
14
post_logout_redirect_uri=post_logout_redirect_uri
15
)
16
logout_url = scalekit.get_logout_url(options)
17
18
# Redirect to Scalekit's logout endpoint
19
# Note: This is a one-time use URL that becomes invalid after use
20
return redirect(logout_url)
```
* Go
```go
1
package main
2
3
import (
4
"net/http"
5
"github.com/gin-gonic/gin"
6
"github.com/scalekit-inc/scalekit-sdk-go"
7
)
8
9
func logoutHandler(c *gin.Context) {
10
// Clear all session data including cookies and local storage
11
clearSessionData()
12
13
// Generate Scalekit logout URL
14
options := scalekit.LogoutUrlOptions{
15
IdTokenHint: idToken,
16
PostLogoutRedirectUri: postLogoutRedirectUri,
17
}
18
logoutUrl, err := scalekitClient.GetLogoutUrl(options)
19
if err != nil {
20
c.JSON(http.StatusInternalServerError, gin.H{
21
"error": "Failed to generate logout URL",
22
})
23
return
24
}
25
26
// Redirect to Scalekit's logout endpoint
27
// Note: This is a one-time use URL that becomes invalid after use
28
c.Redirect(http.StatusFound, logoutUrl.String())
29
}
```
* Java
```java
1
import com.scalekit.internal.http.LogoutUrlOptions;
2
import org.springframework.web.bind.annotation.*;
3
import org.springframework.web.servlet.view.RedirectView;
4
import java.net.URL;
5
6
@RestController
7
public class LogoutController {
8
9
@GetMapping("/logout")
10
public RedirectView logout() {
11
12
clearSessionData();
13
14
15
LogoutUrlOptions options = new LogoutUrlOptions();
16
options.setIdTokenHint(idToken);
17
options.setPostLogoutRedirectUri(postLogoutRedirectUri);
18
19
URL logoutUrl = scalekit.authentication()
20
.getLogoutUrl(options);
21
22
23
// Note: This is a one-time use URL that becomes invalid after use
24
return new RedirectView(logoutUrl.toString());
25
}
26
}
```
The logout process completes when Scalekit invalidates the user’s session and redirects them to your [registered post-logout URL](/guides/dashboard/redirects/#post-logout-url). This single integration unlocks multiple authentication methods, including Magic Link & OTP, social sign-ins, enterprise single sign-on (SSO), and robust user management features. As you continue working with Scalekit, you’ll discover even more features that enhance your authentication workflows.
---
# DOCUMENT BOUNDARY
---
# Add OAuth 2.1 authorization to MCP servers
> Secure your Model Context Protocol (MCP) servers with Scalekit's drop-in OAuth 2.1 authorization solution and protect your AI integrations
This guide shows you how to add production-ready OAuth 2.1 authorization to your Model Context Protocol (MCP) server using Scalekit. You’ll learn how to secure your MCP server so that only authenticated and authorized users can access your tools through AI hosts like Claude Desktop, Cursor, or VS Code. ### Build with a coding agent * Global install (recommended) Terminal
```bash
npm install -g @scalekit-inc/cli
scalekit setup
```
* npx (one-off) Terminal
```bash
npx @scalekit-inc/cli setup
```
MCP servers expose tools that AI hosts can discover and execute to interact with your resources. For example: * A sales team member could use Claude Desktop to view customer information, update records, or set follow-up reminders * A developer could use VS Code or Cursor with a GitHub MCP server to perform everyday GitHub actions through chat * An autonomous agent could use an MCP server to perform actions such as look up the account details in a CRM system When you build MCP servers, multiple AI hosts may need to discover and use your server to interact with your resources. Scalekit handles the complex authentication and authorization for you, so you can focus on building better tools and improving functionality. 1. ## Get Scalekit SDK [Section titled “Get Scalekit SDK”](#get-scalekit-sdk) To get started, make sure you have your Scalekit account and API credentials ready. If you haven’t created a Scalekit account yet, you can [sign up and get a free account](https://app.scalekit.com/ws/signup). Next, install the Scalekit SDK for your language: * Node.js
```bash
npm install @scalekit-sdk/node
```
* Python
```sh
pip install scalekit-sdk-python
```
Use the Scalekit dashboard to register your MCP server and configure MCP hosts (or AI agents following the MCP client protocol) to use Scalekit as the authorization server. The Scalekit SDK validates tokens after users have been authenticated and authorized to access your MCP server. 2. ## Add MCP server to get drop-in OAuth2.1 authorization server [Section titled “Add MCP server to get drop-in OAuth2.1 authorization server”](#add-mcp-server-to-get-drop-in-oauth21-authorization-server) In the Scalekit dashboard, go to **MCP servers** and select **Add MCP server**.  1. Provide a **name** for your MCP server to help you identify it easily. This name appears on the Consent page that MCP hosts display to users when authorizing access to your MCP server. 2. Enable **dynamic client registration** for MCP hosts. This allows MCP hosts to automatically register with Scalekit (and your authorization server), eliminating the need for manual registration and making it easier for users to adopt your MCP server secur. 3. Enable **Client ID Metadata Document (CIMD)** to allow your authorization server to fetch client metadata from MCP hosts and authorize them automatically. 4. Click **Save** to register the server. Note: If your MCP server is intended for use by public MCP clients such as Claude, Cursor, or VS Code, it is recommended to keep both DCR and CIMD enabled. Clients that support CIMD will use the CIMD flow, while clients that do not yet support CIMD can fall back to Dynamic Client Registration. This ensures your MCP server remains compatible with the widest range of MCP clients while preserving a smooth authorization experience. Toggling DCR or CIMD? If you enable or disable DCR or CIMD, be sure to restart your MCP server. Certain MCP frameworks, like FastMCP, cache authorization server details, and a restart ensures the updated configuration is correctly applied. 3. ## Let MCP clients discover your OAuth2.1 authorization server [Section titled “Let MCP clients discover your OAuth2.1 authorization server”](#let-mcp-clients-discover-your-oauth21-authorization-server) MCP protocol directs any MCP client to discover your OAuth2.1 authorization server by calling a public endpoint on your MCP server. This endpoint is called `.well-known/oauth-protected-resource` and your MCP server must host this endpoint.  Copy the resource metadata JSON from **Dashboard > MCP Servers > Your server > Metadata JSON** and implement it in your `.well-known/oauth-protected-resource` endpoint. The `authorization_servers` field contains your Scalekit resource identifier, which clients use to initiate the OAuth flow. * Node.js
```javascript
// MCP client discovery endpoint
// Use case: Allow MCP clients to discover OAuth authorization server configuration
app.get('/.well-known/oauth-protected-resource', (req, res) => {
res.json({
// From Scalekit dashboard > MCP servers > Your server > Metadata JSON
"authorization_servers": [
"https:///resources/"
],
"bearer_methods_supported": [
"header" // Bearer token in Authorization header
],
"resource": "https://mcp.yourapp.com", // Your MCP server URL
"resource_documentation": "https://mcp.yourapp.com/docs", // A URL to the documentation of the resource server
"scopes_supported": ["todo:read", "todo:write"] // Dashboard-configured scopes
});
});
```
* Python
```python
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
# OAuth Protected Resource Metadata endpoint - Required for MCP client discovery
# Copy the actual authorization server URL and metadata from your Scalekit dashboard.
# The values shown here are examples - replace with your actual configuration.
@app.get("/.well-known/oauth-protected-resource")
async def get_oauth_protected_resource():
return JSONResponse({
"authorization_servers": [
"https:///resources/"
],
"bearer_methods_supported": [
"header"
],
"resource": "https://mcp.yourapp.com",
"resource_documentation": "https://mcp.yourapp.com/docs",
"scopes_supported": ["todo:read", "todo:write"]
})
```
4. ## Validate all MCP client requests have a valid access token [Section titled “Validate all MCP client requests have a valid access token”](#validate-all-mcp-client-requests-have-a-valid-access-token) Your MCP server should validate that all incoming requests contain a valid access token. Leverage Scalekit SDKs to validate tokens and verify essential claims such as `aud` (audience), `iss` (issuer), `exp` (expiration), `iat` (issued at), and `scope` (permissions). * Node.js auth-config.js
```javascript
1
import { Scalekit } from '@scalekit-sdk/node';
2
3
// Initialize Scalekit client with environment credentials
4
// Reference installation guide for client setup details
5
const scalekit = new Scalekit(
6
process.env.SCALEKIT_ENVIRONMENT_URL,
7
process.env.SCALEKIT_CLIENT_ID,
8
process.env.SCALEKIT_CLIENT_SECRET
9
);
10
11
// Resource configuration
12
// Get these values from Scalekit dashboard > MCP servers > Your server
13
// For FastMCP: Use base URL with trailing slash (e.g., https://mcp.example.com/)
14
const RESOURCE_ID = 'https://your-mcp-server.com'; // If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard.
15
const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource';
16
17
// WWW-Authenticate header for unauthorized responses
18
// This helps clients understand how to authenticate properly
19
export const WWWHeader = {
20
HeaderKey: 'WWW-Authenticate',
21
HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"`
22
};
```
* Python auth\_config.py
```python
1
from scalekit import ScalekitClient
2
from scalekit.common.scalekit import TokenValidationOptions
3
import os
4
5
# Initialize Scalekit client with environment credentials
6
# Reference installation guide for client setup details
7
scalekit_client = ScalekitClient(
8
env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
9
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
10
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
11
)
12
13
# Resource configuration
14
# Get these values from Scalekit dashboard > MCP servers > Your server
15
# For FastMCP: Use base URL with trailing slash (e.g., https://mcp.example.com/)
16
RESOURCE_ID = "https://your-mcp-server.com" # If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard.
17
METADATA_ENDPOINT = "https://your-mcp-server.com/.well-known/oauth-protected-resource"
18
19
# WWW-Authenticate header for unauthorized responses
20
# This helps clients understand how to authenticate properly
21
WWW_HEADER = {
22
"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{METADATA_ENDPOINT}"'
23
}
```
Extract the Bearer token from incoming MCP client requests. MCP clients send tokens in the `Authorization: Bearer ` header format. * Node.js
```javascript
1
// Extract Bearer token from Authorization header
2
// Use case: Validate requests from AI hosts like Claude Desktop, Cursor, or VS Code
3
const authHeader = req.headers['authorization'];
4
const token = authHeader?.startsWith('Bearer ')
5
? authHeader.split('Bearer ')[1]?.trim()
6
: null;
7
8
if (!token) {
9
throw new Error('Missing or invalid Bearer token');
10
}
```
* Python
```python
1
# Extract Bearer token from Authorization header
2
# Use case: Validate requests from AI hosts like Claude Desktop, Cursor, or VS Code
3
auth_header = request.headers.get("Authorization", "")
4
token = None
5
if auth_header.startswith("Bearer "):
6
token = auth_header.split("Bearer ")[1].strip()
7
8
if not token:
9
raise ValueError("Missing or invalid Bearer token")
```
Validate the token against your configured resource audience to ensure it was issued for your specific MCP server. The resource identifier must match the Server URL you registered earlier. * Node.js Validate token
```javascript
1
// Security: Validate token against configured resource audience
2
// This ensures the token was issued for your specific MCP server
3
await scalekit.validateToken(token, {
4
issuer: ''
5
audience: [RESOURCE_ID]
6
});
```
* Python Validate token
```python
1
# Method 1: validate_access_token - Returns boolean (True/False)
2
# Use this method when you only need to verify token validity without detailed error information.
3
# This approach is suitable for simple authorization checks where you don't need token claims.
4
def validate_token_with_issuer_audience(token: str) -> bool:
5
"""
6
Validates a token and returns True if valid, False otherwise.
7
8
:param token: The token to validate
9
:return: True if token is valid, False otherwise
10
"""
11
options = TokenValidationOptions(
12
issuer="",
13
audience=[RESOURCE_ID]
14
)
15
16
try:
17
is_valid = scalekit_client.validate_access_token(token, options=options)
18
return is_valid
19
except Exception as ex:
20
print(f"Token validation failed: {ex}")
21
return False
22
23
# Method 2: validate_token - Returns token claims/payload
24
# Use this method when you need access to token claims (user info, scopes, etc.) or detailed error information.
25
# This approach is suitable for authorization that requires specific user context or scope validation.
26
def validate_token_and_get_claims(token: str) -> dict:
27
"""
28
Validates a token with specific audience and raises exception on failure.
29
30
:param token: The token to validate
31
:raises: ScalekitValidateTokenFailureException if validation fails
32
"""
33
options = TokenValidationOptions(
34
issuer="",
35
audience=[RESOURCE_ID],
36
required_scopes=["todo:read", "todo:write"] # Optional: validate specific scopes for finer access control
37
)
38
39
scalekit_client.validate_token(token, options=options)
```
* Go middleware/auth.go
```go
1
// Security: validate the token's issuer and audience — confirms it was issued by
2
// your Scalekit environment for your specific MCP server.
3
ok, err := scalekitClient.ValidateTokenWithOptions(ctx, token, &scalekit.ValidateTokenOptions{
4
Issuer: "",
5
Audience: []string{RESOURCE_ID},
6
})
7
if err != nil || !ok {
8
// Reject: return 401 with the WWW-Authenticate header and stop processing.
9
w.Header().Set("WWW-Authenticate", wwwAuthenticateHeader)
10
http.Error(w, "invalid token", http.StatusUnauthorized)
11
return
12
}
13
// Token is valid — continue handling the request.
14
15
// Prefer validating scopes per tool-call rather than in this global check, so each
16
// tool enforces only the scope it needs.
```
* Java middleware/AuthMiddleware.java
```java
1
// Security: validate the token's issuer and audience — confirms it was issued by
2
// your Scalekit environment for your specific MCP server.
3
boolean isValid = scalekitClient.authentication().validateAccessToken(
4
token,
5
TokenValidationOptions.builder()
6
.issuer("")
7
.audience(List.of(RESOURCE_ID))
8
.build()
9
);
10
if (!isValid) {
11
// Reject: return 401 with the WWW-Authenticate header and stop processing.
12
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
13
response.setHeader("WWW-Authenticate", wwwAuthenticateHeader);
14
return;
15
}
16
// Token is valid — continue handling the request.
17
18
// Prefer enforcing scopes per tool-call rather than in this global check: read the
19
// token's "scope" claim via validateAccessTokenAndGetClaims(token, options) inside
20
// each tool and verify it contains the scope that tool needs.
```
#### Complete middleware implementation [Section titled “Complete middleware implementation”](#complete-middleware-implementation) Combine token extraction and validation into a complete authentication middleware that protects all your MCP endpoints. * Node.js
```javascript
import { Scalekit } from '@scalekit-sdk/node';
import { NextFunction, Request, Response } from 'express';
const scalekit = new Scalekit(
process.env.SCALEKIT_ENVIRONMENT_URL,
process.env.SCALEKIT_CLIENT_ID,
process.env.SCALEKIT_CLIENT_SECRET
);
const RESOURCE_ID = 'https://your-mcp-server.com'; // If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard.
const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource';
export const WWWHeader = {
HeaderKey: 'WWW-Authenticate',
HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"`
};
export async function authMiddleware(req: Request, res: Response, next: NextFunction) {
try {
// Security: Allow public access to well-known endpoints for metadata discovery
// This enables MCP clients to discover your OAuth configuration
if (req.path.includes('.well-known')) {
return next();
}
// Extract Bearer token from Authorization header
const authHeader = req.headers['authorization'];
const token = authHeader?.startsWith('Bearer ')
? authHeader.split('Bearer ')[1]?.trim()
: null;
if (!token) {
throw new Error('Missing or invalid Bearer token');
}
// Security: Validate token against configured resource audience
await scalekit.validateToken(token, {
audience: [RESOURCE_ID]
});
next();
} catch (err) {
// Return proper OAuth 2.0 error response with WWW-Authenticate header
return res
.status(401)
.set(WWWHeader.HeaderKey, WWWHeader.HeaderValue)
.end();
}
}
// Apply authentication middleware to all MCP endpoints
app.use('/', authMiddleware);
```
* Python
```python
from scalekit import ScalekitClient
from scalekit.common.scalekit import TokenValidationOptions
from fastapi import Request, HTTPException, status
from fastapi.responses import Response
import os
scalekit_client = ScalekitClient(
env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
)
RESOURCE_ID = "https://your-mcp-server.com" # If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard.
METADATA_ENDPOINT = "https://your-mcp-server.com/.well-known/oauth-protected-resource"
# WWW-Authenticate header for unauthorized responses
WWW_HEADER = {
"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{METADATA_ENDPOINT}"'
}
async def auth_middleware(request: Request, call_next):
# Security: Allow public access to well-known endpoints for metadata discovery
if request.url.path.startswith("/.well-known"):
return await call_next(request)
# Extract Bearer token from Authorization header
auth_header = request.headers.get("Authorization", "")
token = None
if auth_header.startswith("Bearer "):
token = auth_header.split("Bearer ")[1].strip()
if not token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
headers=WWW_HEADER
)
# Security: Validate token against configured resource audience
try:
options = TokenValidationOptions(
issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
audience=[RESOURCE_ID]
)
scalekit_client.validate_token(token, options=options)
except Exception:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
headers=WWW_HEADER
)
return await call_next(request)
# Apply authentication middleware to all MCP endpoints
app.middleware("http")(auth_middleware)
```
5. ## Implement scope-based tool authorization Optional [Section titled “Implement scope-based tool authorization ”](#implement-scope-based-tool-authorization-) Add scope validation at the MCP tool execution level to ensure tools are only executed when the user has authorized the MCP client with the required permissions. This provides fine-grained access control and follows the principle of least privilege. * Node.js
```diff
1
// Security: Validate token has required scope for this specific tool execution
2
// Use case: Ensure users only have access to authorized MCP tools
3
try {
4
await scalekit.validateToken(
5
token, {
6
audience: [RESOURCE_ID],
7
requiredScopes: [scope]
8
}
9
);
10
} catch(error) {
11
// Return OAuth 2.0 compliant error for insufficient scope
12
return res.status(403).json({
13
error: 'insufficient_scope',
14
error_description: `Required scope: ${scope}`,
15
scope: scope
16
});
17
}
```
* Python
```diff
1
# Security: Validate token has required scope for this specific tool execution
2
# Use case: Ensure users only have access to authorized MCP tools
3
try:
4
scalekit_client.validate_access_token(
5
token,
6
options=TokenValidationOptions(
7
audience=[RESOURCE_ID],
8
+required_scopes=[scope]
9
)
10
)
11
except ScalekitValidateTokenFailureException as ex:
12
# Return OAuth 2.0 compliant error for insufficient scope
13
return {
14
"error": "insufficient_scope",
15
"error_description": f"Required scope: {scope}",
16
"scope": scope
17
}
```
6. ## Enable additional authentication methods [Section titled “Enable additional authentication methods”](#enable-additional-authentication-methods) Beyond the OAuth 2.1 authorization you’ve implemented, you can enable additional authentication methods that work seamlessly with your MCP server’s token validation: **[Enterprise SSO](/mcp/auth-methods/enterprise/)** Enable organizations to authenticate through their identity providers (Okta, Azure AD, Google Workspace). Your MCP server continues validating tokens the same way, while Scalekit handles: * Centralized access control through existing enterprise identity systems * Single sign-on experience for organization members * Compliance with corporate security policies **[Social logins](/mcp/auth-methods/social/)** Allow users to authenticate via Google, GitHub, Microsoft, and other social providers. Your existing token validation logic remains unchanged while providing: * Quick onboarding for individual users * Familiar authentication experience * Reduced friction for personal and small team use cases These authentication methods require no changes to your MCP server implementation—you continue validating tokens exactly as shown in the previous steps. **[Bring your own auth](/mcp/auth-methods/custom-auth/)** allows you to use your own authentication system to authenticate users to your MCP server. Your MCP server now has production-ready OAuth 2.1 authorization! You’ve successfully implemented a secure authorization flow that protects your MCP tools and ensures only authenticated users can access them through AI hosts. **Try the demo**: Download and run our [sample MCP server](https://github.com/scalekit-inc/mcp-auth-demos) with authentication already configured to see the complete integration in action. In summary, **Scalekit OAuth authorization server** Acts as the identity provider for your MCP server. * Authenticates users and agents * Issues access tokens with fine-grained scopes * Manages OAuth 2.1 flows (authorization code, client credentials) * Supports dynamic client registration for easy onboarding **Your MCP server** Validates incoming access tokens and enforces the permissions encoded in each token. Only requests with valid, authorized tokens are allowed. This separation of responsibilities ensures a clear boundary: Scalekit handles identity and token issuance, while your MCP server focuses on business logic of executing the actual tool calls.
---
# DOCUMENT BOUNDARY
---
# Add Modular SCIM provisioning
> Automate user provisioning with SCIM. Directory API and webhooks for real-time user data sync
This guide shows you how to automate user provisioning with SCIM using Scalekit’s Directory API and webhooks. You’ll learn to sync user data in real-time, create webhook endpoints for instant updates, and build automated provisioning workflows that keep your application’s user data synchronized with your customers’ directory providers. With [SCIM Provisioning](/directory/guides/user-provisioning-basics) from Scalekit, you can: * Use **webhooks** to listen for events from your customers’ directory providers (e.g., user updates, group changes) * Use **REST APIs** to list users, groups, and directories on demand Scalekit abstracts the complexities of various directory providers, giving you a single interface to automate user lifecycle management. This enables you to create accounts for new hires during onboarding, deactivate accounts when employees depart, and adjust access levels as employees change roles.  ### Build with a coding agent * Recommended Terminal
```bash
npx @scalekit-inc/cli setup
```
* Global install (for repeated use) Terminal
```bash
npm install -g @scalekit-inc/cli
scalekit setup
```
## User provisioning with Scalekit’s directory API [Section titled “User provisioning with Scalekit’s directory API”](#user-provisioning-with-scalekits-directory-api) Scalekit’s directory API allows you to fetch information about users, groups, and directories associated with an organization on-demand. This approach is ideal for scheduled synchronization tasks, bulk data imports, or when you need to ensure your application’s user data matches the latest directory provider state. Let’s explore how to use the Directory API to retrieve user and group data programmatically. 1. ### Setting up the SDK [Section titled “Setting up the SDK”](#setting-up-the-sdk) Before you begin, ensure that your organization [has a directory set up in Scalekit](/guides/user-management/scim-provisioning/). Scalekit offers language-specific SDKs for fast SSO integration. Use the installation instructions below for your technology stack: * Node.js
```bash
npm install @scalekit-sdk/node
```
* Python
```sh
pip install scalekit-sdk-python
```
* Go
```sh
go get -u github.com/scalekit-inc/scalekit-sdk-go
```
* Java
```groovy
/* Gradle users - add the following to your dependencies in build file */
implementation "com.scalekit:scalekit-sdk-java:2.1.3"
```
```xml
com.scalekit
scalekit-sdk-java
2.1.3
```
Navigate to **Dashboard > Developers > Settings > API Credentials** to obtain your credentials. Store your credentials securely in environment variables: .env
```shell
1
# Get these values from Dashboard > Developers > Settings > API Credentials
2
SCALEKIT_ENVIRONMENT_URL='https://b2b-app-dev.scalekit.com'
3
SCALEKIT_CLIENT_ID=''
4
SCALEKIT_CLIENT_SECRET=''
```
2. ### Initialize the SDK and make your first API call [Section titled “Initialize the SDK and make your first API call”](#initialize-the-sdk-and-make-your-first-api-call) Initialize the Scalekit client with your environment variables and make your first API call to list organizations. * cURL Terminal
```bash
1
# Security: Replace with a valid access token from Scalekit
2
# This token authorizes your API requests to access organization data
3
4
# Use case: Verify API connectivity and test authentication
5
# Examples: Initial setup testing, debugging integration issues
6
7
curl -L "https://$SCALEKIT_ENVIRONMENT_URL/api/v1/organizations?page_size=5" \
8
-H "Authorization: Bearer "
```
* Node.js Node.js
```javascript
1
import { ScalekitClient } from '@scalekit-sdk/node';
2
3
// Initialize Scalekit client with environment variables
4
// Security: Always use environment variables for sensitive credentials
5
const scalekit = new ScalekitClient(
6
process.env.SCALEKIT_ENVIRONMENT_URL,
7
process.env.SCALEKIT_CLIENT_ID,
8
process.env.SCALEKIT_CLIENT_SECRET,
9
);
10
11
try {
12
// Use case: Retrieve organizations for bulk user provisioning workflows
13
// Examples: Multi-tenant applications, enterprise customer onboarding
14
const { organizations } = await scalekit.organization.listOrganization({
15
pageSize: 5,
16
});
17
18
console.log(`Organization name: ${organizations[0].display_name}`);
19
console.log(`Organization ID: ${organizations[0].id}`);
20
} catch (error) {
21
console.error('Failed to list organizations:', error);
22
// Handle error appropriately for your application
23
}
```
* Python Python
```python
1
from scalekit import ScalekitClient
2
import os
3
4
# Initialize the SDK client with environment variables
5
# Security: Use os.getenv() to securely access credentials
6
scalekit_client = ScalekitClient(
7
env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
8
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
9
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
10
)
11
12
try:
13
# Use case: Sync user data across multiple organizations
14
# Examples: Scheduled provisioning tasks, HR system integration
15
org_list = scalekit_client.organization.list_organizations(page_size=100)
16
17
if org_list:
18
print(f'Organization details: {org_list[0]}')
19
print(f'Organization ID: {org_list[0].id}')
20
except Exception as error:
21
print(f'Error listing organizations: {error}')
22
# Implement appropriate error handling for your use case
```
* Go Go
```go
1
package main
2
3
import (
4
"context"
5
"fmt"
6
"os"
7
8
"github.com/scalekit/scalekit-go"
9
)
10
11
// Initialize Scalekit client with environment variables
12
// Security: Always load credentials from environment, not hardcoded
13
scalekitClient := scalekit.NewScalekitClient(
14
os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
15
os.Getenv("SCALEKIT_CLIENT_ID"),
16
os.Getenv("SCALEKIT_CLIENT_SECRET"),
17
)
18
19
// Use case: Get specific organization for directory sync operations
20
// Examples: Targeted user provisioning, organization-specific workflows
21
organization, err := scalekitClient.Organization.GetOrganization(
22
ctx,
23
organizationId,
24
)
25
if err != nil {
26
// Handle error appropriately for your application
27
return fmt.Errorf("failed to get organization: %w", err)
28
}
```
* Java Java
```java
1
import com.scalekit.ScalekitClient;
2
3
// Initialize Scalekit client with environment variables
4
// Security: Use System.getenv() to securely access credentials
5
ScalekitClient scalekitClient = new ScalekitClient(
6
System.getenv("SCALEKIT_ENVIRONMENT_URL"),
7
System.getenv("SCALEKIT_CLIENT_ID"),
8
System.getenv("SCALEKIT_CLIENT_SECRET")
9
);
10
11
try {
12
// Use case: List organizations for automated provisioning workflows
13
// Examples: Enterprise customer setup, multi-tenant management
14
ListOrganizationsResponse organizations = scalekitClient.organizations()
15
.listOrganizations(5, "");
16
17
if (!organizations.getOrganizations().isEmpty()) {
18
Organization firstOrg = organizations.getOrganizations().get(0);
19
System.out.println("Organization name: " + firstOrg.getDisplayName());
20
System.out.println("Organization ID: " + firstOrg.getId());
21
}
22
} catch (ScalekitException error) {
23
System.err.println("Failed to list organizations: " + error.getMessage());
24
// Implement appropriate error handling
25
}
```
3. ### Retrieve a directory [Section titled “Retrieve a directory”](#retrieve-a-directory) After successfully listing organizations, you’ll need to retrieve the specific directory to begin syncing user and group data. You can retrieve directories using either the organization and directory IDs, or fetch the primary directory for an organization. * Node.js Node.js
```javascript
1
try {
2
// Use case: Get specific directory when organization has multiple directories
3
// Examples: Department-specific provisioning, multi-division companies
4
const { directory } = await scalekit.directory.getDirectory('', '');
5
console.log(`Directory name: ${directory.name}`);
6
7
// Use case: Get primary directory for simple provisioning workflows
8
// Examples: Small organizations, single-directory setups
9
const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId('');
10
console.log(`Primary directory ID: ${directory.id}`);
11
} catch (error) {
12
console.error('Failed to retrieve directory:', error);
13
// Handle error appropriately for your application
14
}
```
* Python Python
```python
1
try:
2
# Use case: Access specific directory for targeted user sync operations
3
# Examples: Regional offices, business unit-specific provisioning
4
directory = scalekit_client.directory.get_directory(
5
organization_id='', directory_id=''
6
)
7
print(f'Directory name: {directory.name}')
8
9
# Use case: Get primary directory for streamlined user management
10
# Examples: Standard employee provisioning, main company directory
11
primary_directory = scalekit_client.directory.get_primary_directory_by_organization_id(
12
organization_id=''
13
)
14
print(f'Primary directory ID: {primary_directory.id}')
15
except Exception as error:
16
print(f'Error retrieving directory: {error}')
17
# Implement appropriate error handling
```
* Go Go
```go
1
// Use case: Retrieve specific directory for granular access control
2
// Examples: Multi-tenant environments, department-level provisioning
3
directory, err := scalekitClient.Directory().GetDirectory(ctx, organizationId, directoryId)
4
if err != nil {
5
return fmt.Errorf("failed to get directory: %w", err)
6
}
7
fmt.Printf("Directory name: %s\n", directory.Name)
8
9
// Use case: Get primary directory for simplified user management
10
// Examples: Automated provisioning workflows, bulk user imports
11
directory, err := scalekitClient.Directory().GetPrimaryDirectoryByOrganizationId(ctx, organizationId)
12
if err != nil {
13
return fmt.Errorf("failed to get primary directory: %w", err)
14
}
15
fmt.Printf("Primary directory ID: %s\n", directory.ID)
```
* Java Java
```java
1
try {
2
// Use case: Access specific directory for detailed user management
3
// Examples: Custom provisioning logic, directory-specific rules
4
Directory directory = scalekitClient.directories()
5
.getDirectory("", "");
6
System.out.println("Directory name: " + directory.getName());
7
8
// Use case: Get primary directory for standard provisioning workflows
9
// Examples: Employee onboarding, automated user sync
10
Directory primaryDirectory = scalekitClient.directories()
11
.getPrimaryDirectoryByOrganizationId("");
12
System.out.println("Primary directory ID: " + primaryDirectory.getId());
13
} catch (ScalekitException error) {
14
System.err.println("Failed to retrieve directory: " + error.getMessage());
15
// Implement appropriate error handling
16
}
```
4. ### List users in a directory [Section titled “List users in a directory”](#list-users-in-a-directory) Once you have the directory information, you can fetch users within that directory. This is commonly used for bulk user synchronization and maintaining an up-to-date user database. * Node.js Node.js
```javascript
1
try {
2
// Use case: Bulk user synchronization and provisioning
3
// Examples: New customer onboarding, scheduled user data sync
4
const { users } = await scalekit.directory.listDirectoryUsers('', '');
5
6
// Process each user for provisioning or updates
7
users.forEach(user => {
8
console.log(`User email: ${user.email}, Name: ${user.name}`);
9
// TODO: Implement your user provisioning logic here
10
});
11
} catch (error) {
12
console.error('Failed to list directory users:', error);
13
// Handle error appropriately for your application
14
}
```
* Python Python
```python
1
try:
2
# Use case: Automated user provisioning workflows
3
# Examples: HR system integration, bulk user imports
4
directory_users = scalekit_client.directory.list_directory_users(
5
organization_id='', directory_id=''
6
)
7
8
# Process each user for local database updates
9
for user in directory_users:
10
print(f'User email: {user.email}, Name: {user.name}')
11
# TODO: Implement your user synchronization logic here
12
except Exception as error:
13
print(f'Error listing directory users: {error}')
14
# Implement appropriate error handling
```
* Go Go
```go
1
// Configure pagination options for large user directories
2
options := &ListDirectoryUsersOptions{
3
PageSize: 50, // Adjust based on your needs
4
PageToken: "",
5
}
6
7
// Use case: Paginated user retrieval for large directories
8
// Examples: Enterprise customer provisioning, regular sync jobs
9
directoryUsers, err := scalekitClient.Directory().ListDirectoryUsers(ctx, organizationId, directoryId, options)
10
if err != nil {
11
return fmt.Errorf("failed to list directory users: %w", err)
12
}
13
14
// Process each user
15
for _, user := range directoryUsers.Users {
16
fmt.Printf("User email: %s, Name: %s\n", user.Email, user.Name)
17
// TODO: Implement your user provisioning logic
18
}
```
* Java Java
```java
1
// Configure options for user listing with pagination
2
var options = ListDirectoryResourceOptions.builder()
3
.pageSize(50) // Adjust based on your requirements
4
.pageToken("")
5
.includeDetail(true) // Include detailed user information
6
.build();
7
8
try {
9
// Use case: Enterprise user management and synchronization
10
// Examples: Scheduled sync tasks, user provisioning automation
11
ListDirectoryUsersResponse usersResponse = scalekitClient.directories()
12
.listDirectoryUsers(directory.getId(), organizationId, options);
13
14
// Process each user for provisioning
15
for (User user : usersResponse.getUsers()) {
16
System.out.println("User email: " + user.getEmail() + ", Name: " + user.getName());
17
// TODO: Implement your user provisioning logic here
18
}
19
} catch (ScalekitException error) {
20
System.err.println("Failed to list directory users: " + error.getMessage());
21
// Implement appropriate error handling
22
}
```
5. ### List groups in a directory [Section titled “List groups in a directory”](#list-groups-in-a-directory) Groups are essential for implementing role-based access control (RBAC) in your application. After retrieving users, you can fetch groups to manage permissions and access levels based on organizational structure. * Node.js Node.js
```javascript
1
try {
2
// Use case: Role-based access control implementation
3
// Examples: Department-level permissions, project-based access
4
const { groups } = await scalekit.directory.listDirectoryGroups(
5
'',
6
'',
7
);
8
9
// Process each group for RBAC setup
10
groups.forEach(group => {
11
console.log(`Group name: ${group.name}, ID: ${group.id}`);
12
// TODO: Implement your group-based permission logic here
13
});
14
} catch (error) {
15
console.error('Failed to list directory groups:', error);
16
// Handle error appropriately for your application
17
}
```
* Python Python
```python
1
try:
2
# Use case: Department-based access control
3
# Examples: Engineering vs Sales permissions, project team access
4
directory_groups = scalekit_client.directory.list_directory_groups(
5
directory_id='', organization_id=''
6
)
7
8
# Process each group for permission mapping
9
for group in directory_groups:
10
print(f'Group name: {group.name}, ID: {group.id}')
11
# TODO: Implement your group-based permission logic here
12
except Exception as error:
13
print(f'Error listing directory groups: {error}')
14
# Implement appropriate error handling
```
* Go Go
```go
1
// Configure pagination for group listing
2
options := &ListDirectoryGroupsOptions{
3
PageSize: 25, // Adjust based on expected group count
4
PageToken: "",
5
}
6
7
// Use case: Organizational role management
8
// Examples: Enterprise role hierarchy, department-based access
9
directoryGroups, err := scalekitClient.Directory().ListDirectoryGroups(ctx, organizationId, directoryId, options)
10
if err != nil {
11
return fmt.Errorf("failed to list directory groups: %w", err)
12
}
13
14
// Process each group for RBAC implementation
15
for _, group := range directoryGroups.Groups {
16
fmt.Printf("Group name: %s, ID: %s\n", group.Name, group.ID)
17
// TODO: Implement your group-based permission logic
18
}
```
* Java Java
```java
1
// Configure options for detailed group information
2
var options = ListDirectoryResourceOptions.builder()
3
.pageSize(25) // Adjust based on your requirements
4
.pageToken("")
5
.includeDetail(true) // Include group membership details
6
.build();
7
8
try {
9
// Use case: Enterprise permission management
10
// Examples: Role assignments, access level configurations
11
ListDirectoryGroupsResponse groupsResponse = scalekitClient.directories()
12
.listDirectoryGroups(directory.getId(), organizationId, options);
13
14
// Process each group for permission mapping
15
for (Group group : groupsResponse.getGroups()) {
16
System.out.println("Group name: " + group.getName() + ", ID: " + group.getId());
17
// TODO: Implement your group-based permission logic here
18
}
19
} catch (ScalekitException error) {
20
System.err.println("Failed to list directory groups: " + error.getMessage());
21
// Implement appropriate error handling
22
}
```
Scalekit’s Directory API provides a simple way to fetch user and group information on-demand. Refer to our [API reference](https://docs.scalekit.com/apis/) to explore more capabilities. ## Realtime user provisioning with webhooks [Section titled “Realtime user provisioning with webhooks”](#realtime-user-provisioning-with-webhooks) While the Directory API is perfect for scheduled synchronization, webhooks enable immediate, real-time user provisioning. When directory providers send events to Scalekit, we forward them instantly to your application, allowing you to respond to user changes as they happen. This approach is ideal for scenarios requiring immediate action, such as new employee onboarding or emergency access revocation. 1. ### Create a secure webhook endpoint [Section titled “Create a secure webhook endpoint”](#create-a-secure-webhook-endpoint) Create a webhook endpoint to receive real-time events from directory providers. After implementing your endpoint, register it in **Dashboard > Webhooks** where you’ll receive a secret for payload verification. Critical security requirement Always verify webhook signatures before processing events. This prevents unauthorized parties from triggering your provisioning logic and protects against replay attacks. * Node.js Express.js
```javascript
1
app.post('/webhook', async (req, res) => {
2
// Security: ALWAYS verify requests are from Scalekit before processing
3
// This prevents unauthorized parties from triggering your provisioning logic
4
5
const event = req.body;
6
const headers = req.headers;
7
const secret = process.env.SCALEKIT_WEBHOOK_SECRET;
8
9
try {
10
// Verify webhook signature to prevent replay attacks and forged requests
11
await scalekit.verifyWebhookPayload(secret, headers, event);
12
} catch (error) {
13
console.error('Webhook signature verification failed:', error);
14
// Return 400 for invalid signatures - this prevents processing malicious requests
15
return res.status(400).json({ error: 'Invalid signature' });
16
}
17
18
try {
19
// Use case: Real-time user provisioning based on directory events
20
// Examples: New hire onboarding, emergency access revocation, role changes
21
const { email, name } = event.data;
22
23
// Process the webhook event based on its type
24
switch (event.type) {
25
case 'organization.directory.user_created':
26
await createUserAccount(email, name);
27
break;
28
case 'organization.directory.user_updated':
29
await updateUserAccount(email, name);
30
break;
31
case 'organization.directory.user_deleted':
32
await deactivateUserAccount(email);
33
break;
34
default:
35
console.log(`Unhandled event type: ${event.type}`);
36
}
37
38
res.status(201).json({ message: 'Webhook processed successfully' });
39
} catch (processingError) {
40
console.error('Failed to process webhook event:', processingError);
41
res.status(500).json({ error: 'Processing failed' });
42
}
43
});
```
* Python FastAPI
```python
1
from fastapi import FastAPI, Request, HTTPException
2
import os
3
import json
4
5
app = FastAPI()
6
7
@app.post("/webhook")
8
async def api_webhook(request: Request):
9
# Security: ALWAYS verify webhook signatures before processing events
10
# This prevents unauthorized webhook calls and replay attacks
11
12
headers = request.headers
13
body = await request.json()
14
15
try:
16
# Verify webhook payload using the secret from Scalekit dashboard
17
# Get this from Dashboard > Webhooks after registering your endpoint
18
is_valid = scalekit_client.verify_webhook_payload(
19
secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"),
20
headers=headers,
21
payload=json.dumps(body).encode('utf-8')
22
)
23
24
if not is_valid:
25
raise HTTPException(status_code=400, detail="Invalid webhook signature")
26
27
except Exception as verification_error:
28
print(f"Webhook verification failed: {verification_error}")
29
raise HTTPException(status_code=400, detail="Webhook verification failed")
30
31
# Use case: Instant user provisioning based on directory events
32
# Examples: Automated onboarding, immediate access revocation, role updates
33
try:
34
event_type = body.get("type")
35
event_data = body.get("data", {})
36
email = event_data.get("email")
37
name = event_data.get("name")
38
39
if event_type == "organization.directory.user_created":
40
await create_user_account(email, name)
41
elif event_type == "organization.directory.user_updated":
42
await update_user_account(email, name)
43
elif event_type == "organization.directory.user_deleted":
44
await deactivate_user_account(email)
45
46
return JSONResponse(status_code=201, content={"status": "processed"})
47
48
except Exception as processing_error:
49
print(f"Failed to process webhook: {processing_error}")
50
raise HTTPException(status_code=500, detail="Event processing failed")
```
* Java Spring Boot
```java
1
@PostMapping("/webhook")
2
public ResponseEntity webhook(
3
@RequestBody String body,
4
@RequestHeader Map headers) {
5
6
// Security: ALWAYS verify webhook signatures before processing
7
// This prevents malicious webhook calls and protects against replay attacks
8
9
String secret = System.getenv("SCALEKIT_WEBHOOK_SECRET");
10
11
try {
12
// Verify webhook signature using Scalekit SDK
13
boolean isValid = scalekitClient.webhook()
14
.verifyWebhookPayload(secret, headers, body.getBytes());
15
16
if (!isValid) {
17
return ResponseEntity.badRequest().body("Invalid webhook signature");
18
}
19
20
} catch (Exception verificationError) {
21
System.err.println("Webhook verification failed: " + verificationError.getMessage());
22
return ResponseEntity.badRequest().body("Webhook verification failed");
23
}
24
25
try {
26
// Use case: Real-time user lifecycle management
27
// Examples: Employee onboarding, access termination, role modifications
28
ObjectMapper mapper = new ObjectMapper();
29
JsonNode rootNode = mapper.readTree(body);
30
31
String eventType = rootNode.get("type").asText();
32
JsonNode data = rootNode.get("data");
33
34
switch (eventType) {
35
case "organization.directory.user_created":
36
String email = data.get("email").asText();
37
String name = data.get("name").asText();
38
createUserAccount(email, name);
39
break;
40
case "organization.directory.user_updated":
41
updateUserAccount(data);
42
break;
43
case "organization.directory.user_deleted":
44
deactivateUserAccount(data.get("email").asText());
45
break;
46
default:
47
System.out.println("Unhandled event type: " + eventType);
48
}
49
50
return ResponseEntity.status(HttpStatus.CREATED).body("Webhook processed");
51
52
} catch (Exception processingError) {
53
System.err.println("Failed to process webhook event: " + processingError.getMessage());
54
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
55
.body("Event processing failed");
56
}
57
}
```
* Go Go
```go
1
// Security: Store webhook secret securely in environment variables
2
// Get this from Dashboard > Webhooks after registering your endpoint
3
webhookSecret := os.Getenv("SCALEKIT_WEBHOOK_SECRET")
4
5
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
6
// Security: ALWAYS verify webhook signatures before processing events
7
// This prevents unauthorized webhook calls and replay attacks
8
9
if r.Method != http.MethodPost {
10
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
11
return
12
}
13
14
body, err := io.ReadAll(r.Body)
15
if err != nil {
16
http.Error(w, err.Error(), http.StatusBadRequest)
17
return
18
}
19
defer r.Body.Close()
20
21
// Extract webhook headers for verification
22
headers := map[string]string{
23
"webhook-id": r.Header.Get("webhook-id"),
24
"webhook-signature": r.Header.Get("webhook-signature"),
25
"webhook-timestamp": r.Header.Get("webhook-timestamp"),
26
}
27
28
// Verify webhook signature to prevent malicious requests
29
_, err = scalekitClient.VerifyWebhookPayload(webhookSecret, headers, body)
30
if err != nil {
31
http.Error(w, "Invalid webhook signature", http.StatusBadRequest)
32
return
33
}
34
35
// Use case: Instant user provisioning and lifecycle management
36
// Examples: Real-time onboarding, emergency access revocation, role synchronization
37
var webhookEvent WebhookEvent
38
if err := json.Unmarshal(body, &webhookEvent); err != nil {
39
http.Error(w, "Invalid webhook payload", http.StatusBadRequest)
40
return
41
}
42
43
switch webhookEvent.Type {
44
case "organization.directory.user_created":
45
err = createUserAccount(webhookEvent.Data.Email, webhookEvent.Data.Name)
46
case "organization.directory.user_updated":
47
err = updateUserAccount(webhookEvent.Data)
48
case "organization.directory.user_deleted":
49
err = deactivateUserAccount(webhookEvent.Data.Email)
50
default:
51
fmt.Printf("Unhandled event type: %s\n", webhookEvent.Type)
52
}
53
54
if err != nil {
55
http.Error(w, "Failed to process webhook", http.StatusInternalServerError)
56
return
57
}
58
59
w.WriteHeader(http.StatusCreated)
60
w.Write([]byte(`{"status": "processed"}`))
61
})
```
2. ### Register your webhook endpoint [Section titled “Register your webhook endpoint”](#register-your-webhook-endpoint) After implementing your secure webhook endpoint, register it in the Scalekit dashboard to start receiving events: 1. Navigate to **Dashboard > Webhooks** 2. Click **+Add Endpoint** 3. Enter your webhook endpoint URL (e.g., `https://your-app.com/api/webhooks/scalekit`) 4. Add a meaningful description for your reference 5. Select the event types you want to receive. Common choices include: * `organization.directory.user_created` - New user provisioning * `organization.directory.user_updated` - User profile changes * `organization.directory.user_deleted` - User deactivation * `organization.directory.group_created` - New group creation * `organization.directory.group_updated` - Group modifications Once registered, your webhook endpoint will start receiving event payloads from directory providers in real-time. 3. ### Process webhook events [Section titled “Process webhook events”](#process-webhook-events) Scalekit standardizes event payloads across different directory providers, ensuring consistent data structure regardless of whether your customers use Azure AD, Okta, Google Workspace, or other providers. When directory changes occur, Scalekit sends events with the following structure: Webhook event payload
```json
1
{
2
"id": "evt_1234567890",
3
"type": "organization.directory.user_created",
4
"data": {
5
"email": "john.doe@company.com",
6
"name": "John Doe",
7
"organization_id": "org_12345",
8
"directory_id": "dir_67890"
9
},
10
"timestamp": "2024-01-15T10:30:00Z"
11
}
```
You have now successfully implemented and registered a webhook endpoint, enabling your application to receive real-time events for automated user provisioning. Your system can now respond instantly to directory changes, providing seamless user lifecycle management. Refer to our [webhook implementation guide](/authenticate/implement-workflows/implement-webhooks/) for the complete list of available event types and payload structures.
---
# DOCUMENT BOUNDARY
---
# Headless email API for magic link and OTP
> Implement email OTP or magic link using direct API calls with full control over UX
Implement magic link and OTP authentication using Scalekit’s headless APIs. Send either a one-time passcode (OTP) or a magic link to the user’s email, then verify their identity. Magic link and OTP offer two email-based authentication methods, clickable links or one-time passcodes, so users can sign in without passwords. You control the UI and user flows, while Scalekit provides the backend authentication infrastructure. ### Build with a coding agent * Global install (recommended) Terminal
```bash
npm install -g @scalekit-inc/cli
scalekit setup
```
* npx (one-off) Terminal
```bash
npx @scalekit-inc/cli setup
```
*** 1. ## Set up Scalekit [Section titled “Set up Scalekit”](#set-up-scalekit) Install the Scalekit SDK to your project. * Node.js
```bash
npm install @scalekit-sdk/node
```
* Python
```sh
pip install scalekit-sdk-python
```
* Go
```sh
go get -u github.com/scalekit-inc/scalekit-sdk-go
```
* Java
```groovy
/* Gradle users - add the following to your dependencies in build file */
implementation "com.scalekit:scalekit-sdk-java:2.1.3"
```
```xml
com.scalekit
scalekit-sdk-java
2.1.3
```
Your application is responsible for verifying users and initiating sessions, while Scalekit securely manages authentication tokens to ensure the verification process is completed successfully 2. ## Configure magic link and OTP settings [Section titled “Configure magic link and OTP settings”](#configure-magic-link-and-otp-settings) In the Scalekit dashboard, enable magic link and OTP and choose your login method. Optional security settings: * **Enforce same-browser origin**: Users must complete magic-link auth in the same browser they started in. * **Issue new credentials on resend**: Each resend generates a fresh code or link and invalidates the previous one.  3. ## Send verification email [Section titled “Send verification email”](#send-verification-email) The first step in the magic link and OTP flow is to send a verification email to the user’s email address. This email contains either a **one-time passcode (OTP), a magic link, or both** based on your selection in the Scalekit dashboard. Follow these steps to implement the verification email flow: 1. Create a form to collect the user’s email address 2. Call the passwordless API (magic link and OTP) when the form is submitted 3. Handle the response to provide feedback to the user API endpoint
```http
POST /api/v1/passwordless/email/send
```
**Example implementation** * cURL Send a verification code to user's email
```sh
1
curl -L '/api/v1/passwordless/email/send' \
2
-H 'Content-Type: application/json' \
3
-H 'Authorization: Bearer eyJh..' \
4
--data-raw '{
5
"email": "john.doe@example.com",
6
"expires_in": 300,
7
"state": "jAy-state1-gM4fdZ...2nqm6Q",
8
"template": "SIGNIN",
9
10
"magiclink_auth_uri": "https://yourapp.com/passwordless/verify",
11
"template_variables": {
12
"custom_variable_key": "custom_variable_value"
13
}
14
}'
15
16
# Response
17
# {
18
# "auth_request_id": "jAy-state1-gM4fdZ...2nqm6Q"
19
# "expires_at": "1748696575"
20
# "expires_in": 100
21
# "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
22
# }
```
* Node.js
```js
1
const options = {
2
template: "SIGNIN",
3
state: "jAy-state1-...2nqm6Q",
4
expiresIn: 300,
5
// Required if you selected Link or Link+OTP as your authentication method
6
magiclinkAuthUri: "https://yourapp.com/passwordless/verify",
7
templateVariables: {
8
employeeID: "EMP523",
9
teamName: "Alpha Team",
10
},
11
};
12
13
const sendResponse = await scalekit.passwordless
14
.sendPasswordlessEmail(
15
"",
16
options
17
);
18
19
// sendResponse = {
20
// authRequestId: string,
21
// expiresAt: number, // seconds since epoch
22
// expiresIn: number, // seconds
23
// passwordlessType: string // "OTP" | "LINK" | "LINK_OTP"
24
// }
```
* Python
```python
1
response = client.passwordless.send_passwordless_email(
2
email="john.doe@example.com",
3
template="SIGNIN", # or "SIGNUP", "UNSPECIFIED"
4
expires_in=300,
5
magiclink_auth_uri="https://yourapp.com/passwordless/verify",
6
template_variables={
7
"employeeID": "EMP523",
8
"teamName": "Alpha Team",
9
},
10
)
11
12
# Extract auth request ID from response
13
auth_request_id = response[0].auth_request_id
```
* Go
```go
1
// Send a passwordless email (assumes you have an initialized `client` and `ctx`)
2
templateType := scalekit.TemplateTypeSignin
3
resp, err := scalekitClient.Passwordless().SendPasswordlessEmail(
4
ctx,
5
"john.doe@example.com",
6
&scalekit.SendPasswordlessOptions{
7
Template: &templateType,
8
State: "jAy-state1-gM4fdZ...2nqm6Q",
9
ExpiresIn: 300,
10
MagiclinkAuthUri: "https://yourapp.com/passwordless/verify", // required if Link or Link+OTP
11
TemplateVariables: map[string]string{
12
"employeeID": "EMP523",
13
"teamName": "Alpha Team",
14
},
15
},
16
)
17
18
// resp contains: AuthRequestId, ExpiresAt, ExpiresIn, PasswordlessType
```
* Java
```java
1
import java.util.HashMap;
2
import java.util.Map;
3
4
TemplateType templateType = TemplateType.SIGNIN;
5
Map templateVariables = new HashMap<>();
6
templateVariables.put("employeeID", "EMP523");
7
templateVariables.put("teamName", "Alpha Team");
8
9
SendPasswordlessOptions options = new SendPasswordlessOptions();
10
options.setTemplate(templateType);
11
options.setExpiresIn(300);
12
options.setMagiclinkAuthUri("https://yourapp.com/passwordless/verify");
13
options.setTemplateVariables(templateVariables);
14
15
SendPasswordlessResponse response = passwordlessClient.sendPasswordlessEmail(
16
"john.doe@example.com",
17
options
18
);
19
20
String authRequestId = response.getAuthRequestId();
```
4. ### Resend a verification email [Section titled “Resend a verification email”](#resend-a-verification-email) Users can request a new verification email if they need one. Use the following endpoint to resend an OTP or magic link email. * cURL Request
```diff
1
curl -L '/api/v1/passwordless/email/resend' \
2
-H 'Content-Type: application/json' \
3
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
4
-d '{
5
"auth_request_id": "jAy-state1-gM4fdZ...2nqm6Q"
6
}'
7
8
# Response
9
10
# {
11
12
# "auth_request_id": "jAy-state1-gM4fdZ...2nqm6Q"
13
14
# "expires_at": "1748696575"
15
16
# "expires_in": 300
17
18
# "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
19
20
# }
```
* Node.js
```js
1
const { authRequestId } = sendResponse;
2
const resendResponse = await scalekit.passwordless
3
.resendPasswordlessEmail(
4
authRequestId
5
);
6
7
// resendResponse = {
8
// authRequestId: "jAy-state1-gM4fdZ...2nqm6Q",
9
// expiresAt: "1748696575",
10
// expiresIn: "300",
11
// passwordlessType: "OTP" | "LINK" | "LINK_OTP"
12
// }
```
* Python
```python
1
resend_response = client.passwordless.resend_passwordless_email(
2
auth_request_id=auth_request_id,
3
)
4
5
new_auth_request_id = resend_response[0].auth_request_id
```
* Go
```go
1
// Resend passwordless email for an existing auth request
2
resendResp, err := scalekitClient.Passwordless().ResendPasswordlessEmail(
3
ctx, // context.Context (e.g., context.Background())
4
authRequestId, // string: from the send email response
5
)
6
7
if err != nil {
8
// handle error (log, return HTTP 400/500, etc.)
9
// ...
10
}
11
12
// resendResp is a pointer to ResendPasswordlessResponse struct:
13
// type ResendPasswordlessResponse struct {
14
// AuthRequestId string // Unique ID for the passwordless request
15
// ExpiresAt int64 // Unix timestamp (seconds since epoch)
16
// ExpiresIn int // Expiry duration in seconds
17
// PasswordlessType string // "OTP", "LINK", or "LINK_OTP"
18
// }
```
* Java
```java
SendPasswordlessResponse resendResponse = passwordlessClient.resendPasswordlessEmail(authRequestId);
```
If you enabled **Enable new Magic link & OTP credentials on resend** in the Scalekit dashboard, a new verification code or magic link will be sent each time the user requests a new one. Rate limits Scalekit enforces a rate limit of 2 magic link and OTP emails per minute per email address. This limit includes both initial sends and resends. 5. ### Verify the user’s identity [Section titled “Verify the user’s identity”](#verify-the-users-identity) Once the user receives the verification email, * If it is a verification code, they’ll enter it in your application. Use the following endpoint to validate the code and complete authentication. * If it is a magic link, they’ll click the link in the email to verify their address. Capture the `link_token` query parameter and use it to verify. * For additional security with magic links, if you enabled “Enforce same browser origin” in the dashboard, include the `auth_request_id` in the verification request. - Verification code 1. Create a form to collect the verification code 2. Call the verification API when the form is submitted to verify the code 3. Handle the response to either grant access or show an error API endpoint
```http
POST /api/v1/passwordless/email/verify
```
**Example implementation** * cURL Request
```diff
curl -L '/api/v1/passwordless/email/verify' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
-d '{
"code": "123456",
"auth_request_id": "YC4QR-dVZVtNNVHcHwrnHNDV..."
}'
```
* Node.js
```js
1
const { authRequestId } = sendResponse;
2
const verifyResponse = await scalekit.passwordless
3
.verifyPasswordlessEmail(
4
{ code: "123456"},
5
authRequestId
6
);
7
8
// verifyResponse = {
9
// "email": "saifshine7@gmail.com",
10
// "state": "jAy-state1-gM4fdZdV22nqm6Q_j..",
11
// "template": "SIGNIN",
12
// "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
13
// }
```
* Python
```python
1
verify_response = client.passwordless.verify_passwordless_email(
2
code="123456", # OTP code received via email
3
auth_request_id=auth_request_id,
4
)
5
6
# User verified successfully
7
user_email = verify_response[0].email
```
* Go
```go
1
// Verify with OTP code
2
verifyResponse, err := scalekitClient.Passwordless().VerifyPasswordlessEmail(
3
ctx,
4
&scalekit.VerifyPasswordlessOptions{
5
Code: "123456", // OTP code
6
AuthRequestId: authRequestId,
7
},
8
)
9
10
if err != nil {
11
// Handle error
12
return
13
}
14
15
// verifyResp contains the verified user's info
16
// type VerifyPasswordLessResponse struct {
17
// Email string
18
// State string
19
// Template string // SIGNIN | SIGNUP
20
// PasswordlessType string // OTP | LINK | LINK_OTP
21
// }
```
* Java
```java
1
// Verify with OTP code
2
VerifyPasswordlessOptions verifyOptions = new VerifyPasswordlessOptions();
3
verifyOptions.setCode("123456"); // OTP code
4
verifyOptions.setAuthRequestId(authRequestId);
5
6
VerifyPasswordLessResponse verifyResponse = passwordlessClient.verifyPasswordlessEmail(verifyOptions);
7
8
// User verified successfully
9
String userEmail = verifyResponse.getEmail();
```
- Magic link verification Request
```diff
curl -L '/api/v1/passwordless/email/verify' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
-d '{
"code": "123456",
"auth_request_id": "YC4QR-dVZVtNNVHcHwrnHNDV..."
}'
```
- cURL
```js
1
const { authRequestId } = sendResponse;
2
const verifyResponse = await scalekit.passwordless
3
.verifyPasswordlessEmail(
4
{ code: "123456"},
5
authRequestId
6
);
7
8
// verifyResponse = {
9
// "email": "saifshine7@gmail.com",
10
// "state": "jAy-state1-gM4fdZdV22nqm6Q_j..",
11
// "template": "SIGNIN",
12
// "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
13
// }
```
- Node.js
```python
1
verify_response = client.passwordless.verify_passwordless_email(
2
code="123456", # OTP code received via email
3
auth_request_id=auth_request_id,
4
)
5
6
# User verified successfully
7
user_email = verify_response[0].email
```
- Python
```go
1
// Verify with OTP code
2
verifyResponse, err := scalekitClient.Passwordless().VerifyPasswordlessEmail(
3
ctx,
4
&scalekit.VerifyPasswordlessOptions{
5
Code: "123456", // OTP code
6
AuthRequestId: authRequestId,
7
},
8
)
9
10
if err != nil {
11
// Handle error
12
return
13
}
14
15
// verifyResp contains the verified user's info
16
// type VerifyPasswordLessResponse struct {
17
// Email string
18
// State string
19
// Template string // SIGNIN | SIGNUP
20
// PasswordlessType string // OTP | LINK | LINK_OTP
21
// }
```
- Go
```java
1
// Verify with OTP code
2
VerifyPasswordlessOptions verifyOptions = new VerifyPasswordlessOptions();
3
verifyOptions.setCode("123456"); // OTP code
4
verifyOptions.setAuthRequestId(authRequestId);
5
6
VerifyPasswordLessResponse verifyResponse = passwordlessClient.verifyPasswordlessEmail(verifyOptions);
7
8
// User verified successfully
9
String userEmail = verifyResponse.getEmail();
```
- Java To support magic link verification, add a callback endpoint in your application typically at `https://your-app.com/passwordless/verify`. Implement it to verify the magic link token and complete the user authentication process. 1. Create a verification endpoint in your application to handle the magic link verification. This is the endpoint that the user lands in when they click the link in the email. 2. Capture the magic link token from the `link_token` request parameter from the URL. 3. Call the verification API when the user clicks the link in the email. 4. Based on token verification, complete the authentication process or show an error with an appropriate error message. API endpoint
```http
POST /api/v1/passwordless/email/verify
```
**Example implementation** * cURL Request
```diff
curl -L '/api/v1/passwordless/email/verify' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
-d '{
"link_token": "a4143d8f-...c846ed91e_l",
"auth_request_id": "YC4QR-dVZVtNNVHcHwrnHNDV..." // (optional)
}'
```
* Node.js
```js
1
// User clicks the magic link in their email
2
// Example magic link: https://yourapp.com/passwordless/verify?link_token=a4143d8f-d13d-415c-8f5a-5a5c846ed91e_l
3
4
// 2. Express endpoint to handle the magic link verification
5
app.get('/passwordless/verify', async (req, res) => {
6
const { link_token } = req.query;
7
8
try {
9
// 3. Verify the magic link token with Scalekit
10
const verifyResponse = await scalekit.passwordless
11
.verifyPasswordlessEmail(
12
{ linkToken: link_token },
13
authRequestId // (optional) sendResponse.authRequestId
14
);
15
16
// 4. Successfully log the user in
17
// Set session/token and redirect to dashboard
18
res.redirect('/dashboard');
19
} catch (error) {
20
res.status(400).json({
21
error: 'The magic link is invalid or has expired. Please request a new verification link.'
22
});
23
}
24
});
25
26
// verifyResponse = {
27
// "email": "saifshine7@gmail.com",
28
// "state": "jAy-state1-gM4fdZdV22nqm6Q_j..",
29
// "template": "SIGNIN",
30
// "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
31
// }
```
* Python
```python
1
# Verify with magic link token
2
verify_response = client.passwordless.verify_passwordless_email(
3
link_token=link_token, # Magic link token from URL
4
# auth_request_id=auth_request_id, # optional if same-origin enforcement enabled
5
)
6
7
# User verified successfully
8
user_email = verify_response[0].email
```
* Go
```go
1
verifyResponse, err := scalekitClient.Passwordless().VerifyPasswordlessEmail(
2
ctx,
3
&scalekit.VerifyPasswordlessOptions{
4
LinkToken: linkToken, // Magic link token
5
},
6
)
7
8
if err != nil {
9
// Handle error
10
return
11
}
12
13
// User verified successfully
14
userEmail := verifyResponse.Email
```
* Java
```java
1
// Verify with magic link token
2
VerifyPasswordlessOptions verifyOptions = new VerifyPasswordlessOptions();
3
verifyOptions.setLinkToken(linkToken); // Magic link token
4
// verifyOptions.setAuthRequestId(authRequestId); // optional if same-origin enforcement enabled
5
6
VerifyPasswordLessResponse verifyResponse = passwordlessClient.verifyPasswordlessEmail(verifyOptions);
7
8
// User verified successfully
9
String userEmail = verifyResponse.getEmail();
```
- cURL Request
```diff
curl -L '/api/v1/passwordless/email/verify' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
-d '{
"link_token": "a4143d8f-...c846ed91e_l",
"auth_request_id": "YC4QR-dVZVtNNVHcHwrnHNDV..." // (optional)
}'
```
- Node.js
```js
1
// User clicks the magic link in their email
2
// Example magic link: https://yourapp.com/passwordless/verify?link_token=a4143d8f-d13d-415c-8f5a-5a5c846ed91e_l
3
4
// 2. Express endpoint to handle the magic link verification
5
app.get('/passwordless/verify', async (req, res) => {
6
const { link_token } = req.query;
7
8
try {
9
// 3. Verify the magic link token with Scalekit
10
const verifyResponse = await scalekit.passwordless
11
.verifyPasswordlessEmail(
12
{ linkToken: link_token },
13
authRequestId // (optional) sendResponse.authRequestId
14
);
15
16
// 4. Successfully log the user in
17
// Set session/token and redirect to dashboard
18
res.redirect('/dashboard');
19
} catch (error) {
20
res.status(400).json({
21
error: 'The magic link is invalid or has expired. Please request a new verification link.'
22
});
23
}
24
});
25
26
// verifyResponse = {
27
// "email": "saifshine7@gmail.com",
28
// "state": "jAy-state1-gM4fdZdV22nqm6Q_j..",
29
// "template": "SIGNIN",
30
// "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
31
// }
```
- Python
```python
1
# Verify with magic link token
2
verify_response = client.passwordless.verify_passwordless_email(
3
link_token=link_token, # Magic link token from URL
4
# auth_request_id=auth_request_id, # optional if same-origin enforcement enabled
5
)
6
7
# User verified successfully
8
user_email = verify_response[0].email
```
- Go
```go
1
verifyResponse, err := scalekitClient.Passwordless().VerifyPasswordlessEmail(
2
ctx,
3
&scalekit.VerifyPasswordlessOptions{
4
LinkToken: linkToken, // Magic link token
5
},
6
)
7
8
if err != nil {
9
// Handle error
10
return
11
}
12
13
// User verified successfully
14
userEmail := verifyResponse.Email
```
- Java
```java
1
// Verify with magic link token
2
VerifyPasswordlessOptions verifyOptions = new VerifyPasswordlessOptions();
3
verifyOptions.setLinkToken(linkToken); // Magic link token
4
// verifyOptions.setAuthRequestId(authRequestId); // optional if same-origin enforcement enabled
5
6
VerifyPasswordLessResponse verifyResponse = passwordlessClient.verifyPasswordlessEmail(verifyOptions);
7
8
// User verified successfully
9
String userEmail = verifyResponse.getEmail();
```
Validation attempt limits To protect your application, Scalekit allows a user only **five** attempts to enter the correct OTP within a ten-minute window. If the user exceeds this limit for an `auth_request_id`, the `/passwordless/email/verify` endpoint returns an **HTTP 429 Too Many Requests** error. To continue, the user must restart the authentication flow. You’ve successfully implemented Magic link & OTP authentication in your application. Users can now sign in securely without passwords by entering a verification code (OTP) or clicking a magic link sent to their email.
---
# DOCUMENT BOUNDARY
---
# Quickstart: Deploy Scalekit on Kubernetes
> Deploy Scalekit on Kubernetes in minutes using bundled databases and a single values.yaml file.
Get Scalekit running on Kubernetes quickly through the Scalekit distribution portal. This guide uses bundled PostgreSQL and Redis and auto-creates Kubernetes secrets from your `values.yaml`. No external databases or `kubectl secret` commands are needed. ## Before you start [Section titled “Before you start”](#before-you-start) | Requirement | Notes | | ----------------------- | -------------------------------------------------------------------------- | | `kubectl` 1.27+ | Configured against your target cluster | | Helm 3.12+ | Run `helm version` to verify | | Domain | Subdomains `app.` and `auth.` must resolve to your cluster | | Gateway class | A GatewayClass must be installed on your cluster | | TLS certificate | Attached via a gateway annotation (GCP cert map, cert-manager, etc.) | | SMTP credentials | Any provider; Postmark and SendGrid have first-class support | | Registry token | From the Scalekit distribution portal (see step 1) | | `openssl` and `python3` | To generate webhook credentials in step 2 | *** 1. ### Get a registry token [Section titled “Get a registry token”](#get-a-registry-token) Log in to the Scalekit distribution portal and create a Personal Access Token. This token authenticates your cluster to pull Scalekit images from the Scalekit container registry. Copy the token — it is shown only once. Note the expiry date and rotate before it lapses to avoid `ImagePullBackOff` errors on new deployments. 2. ### Generate webhook credentials [Section titled “Generate webhook credentials”](#generate-webhook-credentials) Run these commands to produce a JWT secret and a signed API token. Copy both output lines. You will paste them into `values.yaml` in the next step.
```bash
1
export JWT_SECRET=$(openssl rand -base64 32)
2
echo "jwtSecret: $JWT_SECRET"
3
4
python3 << 'EOF'
5
import base64, hashlib, hmac as _hmac, json, os, secrets, string, time
6
7
secret = os.environ['JWT_SECRET']
8
chars = string.ascii_letters + string.digits
9
sub = 'org_' + ''.join(secrets.choice(chars) for _ in range(22))
10
now = int(time.time())
11
exp = now + 315360000
12
13
h = base64.urlsafe_b64encode(json.dumps({'alg':'HS256','typ':'JWT'}, separators=(',',':')).encode()).rstrip(b'=').decode()
14
p = base64.urlsafe_b64encode(json.dumps({'iat':now,'exp':exp,'nbf':now,'iss':'svix-server','sub':sub}, separators=(',',':')).encode()).rstrip(b'=').decode()
15
msg = f'{h}.{p}'
16
sig = base64.urlsafe_b64encode(_hmac.new(secret.encode(), msg.encode(), hashlib.sha256).digest()).rstrip(b'=').decode()
17
print(f'apiToken: {msg}.{sig}')
18
EOF
```
The output looks like:
```plaintext
1
jwtSecret: aB3cD4eF5gH6...
2
apiToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
3. ### Create values.yaml [Section titled “Create values.yaml”](#create-valuesyaml) Create a `values.yaml` file using the template below. Replace each placeholder with your actual value. values.yaml
```yaml
1
scalekit:
2
config:
3
app:
4
domain: "" # e.g. scalekit.example.com — no scheme or trailing slash
5
seedData:
6
adminUser:
7
firstName: ""
8
lastName: ""
9
email: ""
10
emailServer:
11
settings:
12
fromEmail: "hi@"
13
fromName: "Team "
14
host: ""
15
port:
16
username: ""
17
18
postgresql:
19
enabled: true
20
21
redis:
22
enabled: true
23
24
secrets:
25
create: true
26
svix:
27
jwtSecret: ""
28
apiToken: ""
29
registry:
30
password: ""
31
32
gateway:
33
enabled: true
34
provider: "" # gcp for GKE; other for all other clusters
35
className: "" # e.g. gke-l7-global-external-managed
36
annotations:
37
: "" # e.g. networking.gke.io/certmap: your-cert-map
38
redirectToHttps: true
39
healthCheckPolicy:
40
enabled: true # GKE only — set false for other providers
```
| Field | Description | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `domain` | Base domain. The chart derives `app.` and `auth.` from this. | | `adminUser` | First admin account created on first boot. Use this to log in to the dashboard. | | `emailServer` | SMTP settings for transactional email (invites, magic links, verification codes). The password is provided separately via `secrets.smtp.password` (see [full configuration](/self-hosted/configuration/) for details). | | `secrets.create` | When `true`, the chart creates all required Kubernetes secrets from the values below. | | `secrets.svix` | Webhook service credentials generated in step 2. | | `secrets.registry.password` | Registry access token from step 1. | | `gateway.provider` | Set to `gcp` for GKE to enable GKE-specific resources. Set to `other` for all other providers. | | `gateway.className` | The GatewayClass installed on your cluster. | | `gateway.annotations` | Attach a TLS certificate via your provider’s annotation. | 4. ### Create a deployment in the portal [Section titled “Create a deployment in the portal”](#create-a-deployment-in-the-portal) Deployments are managed through the Scalekit distribution portal. Create the namespace on your cluster first:
```bash
1
kubectl create namespace
```
Then, in the portal: * In the left sidebar, click **Deployments**, then **+ New Deployment** * Select **Scalekit Onprem** and click **Continue** * Set a **Deployment Name** (`scalekit` is recommended) and enter the **Kubernetes Namespace** you just created * Leave **Enable cluster-scoped permissions** checked and click **Continue** * Select the **Version** you want to install * Under **Helm values**, paste the full contents of your `values.yaml` * Click **Create Deployment** The portal shows a **Deployment Created Successfully** screen with a `kubectl apply` command. Click **Copy Command** and run it on your cluster:
```bash
1
kubectl apply -n -f ""
```
This connects your cluster to the portal, which then installs the Helm chart and runs database migrations. 5. ### Update DNS [Section titled “Update DNS”](#update-dns) Once the gateway is provisioned, get its external IP:
```bash
1
kubectl get gateway -n scalekit
```
Copy the address from the `ADDRESS` column. In your DNS provider, create a wildcard `A` record:
```plaintext
1
*. →
```
DNS propagation can take a few minutes. Verify with:
```bash
1
dig app.
```
6. ### Verify [Section titled “Verify”](#verify)
```bash
1
kubectl get pods -n scalekit
```
All pods should show `Running`. Open `https://app.` in a browser and sign in with the admin email you set in `values.yaml`. ## Next steps [Section titled “Next steps”](#next-steps) Next, System requirements will confirm your cluster, databases, and network are ready before you proceed. * Ready for production? Switch to external databases and full secret management. Follow the [installation guide](/self-hosted/installation/). * Review every configuration field in the [configuration reference](/self-hosted/configuration/). * Learn how to upgrade and maintain your deployment in [Upgrades and maintenance](/self-hosted/upgrades/). Not for production The bundled databases have no backups, no replication, and no persistent storage guarantees. For a production deployment with external services and full secret management, follow the [installation guide](/self-hosted/installation/).
---
# DOCUMENT BOUNDARY
---
# Modular SSO quickstart
> Enable enterprise SSO for any customer in minutes with built-in SAML and OIDC integrations
Enterprise customers often require Single Sign-On (SSO) support for their applications. Rather than building custom integrations for every identity provider such as Okta, Entra ID, or JumpCloud and managing their OIDC and SAML protocols, you can let Scalekit handle those connections with each of your customer’s identity providers. Modular SSO is designed for applications that maintain their own user database and session management. This lightweight integration focuses solely on identity verification, giving you complete control over user data and authentication flows. Choose Modular SSO when you: * Want to manage user records in your own database * Prefer to implement custom session management logic * Need to integrate SSO without changing your existing authentication architecture * Already have existing user management infrastructure ### Build with a coding agent * Recommended Terminal
```bash
npx @scalekit-inc/cli setup
```
* Global install (for repeated use) Terminal
```bash
npm install -g @scalekit-inc/cli
scalekit setup
```
1. ## Set up Scalekit [Section titled “Set up Scalekit”](#set-up-scalekit) Use the following instructions to install the SDK for your technology stack. * Node.js
```bash
npm install @scalekit-sdk/node
```
* Python
```sh
pip install scalekit-sdk-python
```
* Go
```sh
go get -u github.com/scalekit-inc/scalekit-sdk-go
```
* Java
```groovy
/* Gradle users - add the following to your dependencies in build file */
implementation "com.scalekit:scalekit-sdk-java:2.1.3"
```
```xml
com.scalekit
scalekit-sdk-java
2.1.3
```
Since we will using Modular SSO, you need to disable complete auth: 1. Go to Dashboard > Authentication > General 2. Under “Full-Stack Auth” section, click “Disable Full-Stack Auth” Now you’re ready to start integrating SSO into your app! 2. ## Redirect the users to their enterprise identity provider login page [Section titled “Redirect the users to their enterprise identity provider login page”](#redirect-the-users-to-their-enterprise-identity-provider-login-page) Use the Scalekit SDK to construct authorization URL with your redirect URI and required scopes. Scalekit will automatically redirect the user to the user’s enterprise identity provider login page to authenticate. * Node.js authorization-url.js
```javascript
1
import { Scalekit } from '@scalekit-sdk/node';
2
3
const scalekit = new ScalekitClient(
4
'', // Your Scalekit environment URL
5
'', // Unique identifier for your app
6
'',
7
);
8
9
const options = {};
10
11
// Specify which SSO connection to use (choose one based on your use case)
12
// These identifiers are evaluated in order of precedence:
13
14
// 1. connectionId (highest precedence) - Use when you know the exact SSO connection
15
options['connectionId'] = 'conn_15696105471768821';
16
17
// 2. organizationId - Routes to organization's SSO (useful for multi-tenant apps)
18
// If org has multiple connections, the first active one is selected
19
options['organizationId'] = 'org_15421144869927830';
20
21
// 3. loginHint (lowest precedence) - Extracts domain from email to find connection
22
// Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding)
23
options['loginHint'] = 'user@example.com';
24
25
// redirect_uri: Your callback endpoint that receives the authorization code
26
// Must match the URL registered in your Scalekit dashboard
27
const redirectUrl = 'https://your-app.com/auth/callback';
28
29
const authorizationURL = scalekit.getAuthorizationUrl(redirectUrl, options);
30
// Redirect user to this URL to begin SSO authentication
```
* Python authorization\_url.py
```python
1
from scalekit import ScalekitClient, AuthorizationUrlOptions
2
3
scalekit = ScalekitClient(
4
'', # Your Scalekit environment URL
5
'', # Unique identifier for your app
6
''
7
)
8
9
options = AuthorizationUrlOptions()
10
11
# Specify which SSO connection to use (choose one based on your use case)
12
# These identifiers are evaluated in order of precedence:
13
14
# 1. connection_id (highest precedence) - Use when you know the exact SSO connection
15
options.connection_id = 'conn_15696105471768821'
16
17
# 2. organization_id - Routes to organization's SSO (useful for multi-tenant apps)
18
# If org has multiple connections, the first active one is selected
19
options.organization_id = 'org_15421144869927830'
20
21
# 3. login_hint (lowest precedence) - Extracts domain from email to find connection
22
# Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding)
23
options.login_hint = 'user@example.com'
24
25
# redirect_uri: Your callback endpoint that receives the authorization code
26
# Must match the URL registered in your Scalekit dashboard
27
redirect_uri = 'https://your-app.com/auth/callback'
28
29
authorization_url = scalekit_client.get_authorization_url(
30
redirect_uri=redirect_uri,
31
options=options
32
)
33
# Redirect user to this URL to begin SSO authentication
```
* Go authorization\_url.go
```go
1
import (
2
"github.com/scalekit-inc/scalekit-sdk-go"
3
)
4
5
func main() {
6
scalekitClient := scalekit.NewScalekitClient(
7
"", // Your Scalekit environment URL
8
"", // Unique identifier for your app
9
""
10
)
11
12
options := scalekitClient.AuthorizationUrlOptions{}
13
14
// Specify which SSO connection to use (choose one based on your use case)
15
// These identifiers are evaluated in order of precedence:
16
17
// 1. ConnectionId (highest precedence) - Use when you know the exact SSO connection
18
options.ConnectionId = "conn_15696105471768821"
19
20
// 2. OrganizationId - Routes to organization's SSO (useful for multi-tenant apps)
21
// If org has multiple connections, the first active one is selected
22
options.OrganizationId = "org_15421144869927830"
23
24
// 3. LoginHint (lowest precedence) - Extracts domain from email to find connection
25
// Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding)
26
options.LoginHint = "user@example.com"
27
28
// redirectUrl: Your callback endpoint that receives the authorization code
29
// Must match the URL registered in your Scalekit dashboard
30
redirectUrl := "https://your-app.com/auth/callback"
31
32
authorizationURL := scalekitClient.GetAuthorizationUrl(
33
redirectUrl,
34
options,
35
)
36
// Redirect user to this URL to begin SSO authentication
37
}
```
* Java AuthorizationUrl.java
```java
1
package com.scalekit;
2
3
import com.scalekit.ScalekitClient;
4
import com.scalekit.internal.http.AuthorizationUrlOptions;
5
6
public class Main {
7
8
public static void main(String[] args) {
9
ScalekitClient scalekitClient = new ScalekitClient(
10
"", // Your Scalekit environment URL
11
"", // Unique identifier for your app
12
""
13
);
14
15
AuthorizationUrlOptions options = new AuthorizationUrlOptions();
16
17
// Specify which SSO connection to use (choose one based on your use case)
18
// These identifiers are evaluated in order of precedence:
19
20
// 1. connectionId (highest precedence) - Use when you know the exact SSO connection
21
options.setConnectionId("con_13388706786312310");
22
23
// 2. organizationId - Routes to organization's SSO (useful for multi-tenant apps)
24
// If org has multiple connections, the first active one is selected
25
options.setOrganizationId("org_13388706786312310");
26
27
// 3. loginHint (lowest precedence) - Extracts domain from email to find connection
28
// Domain must be registered to the organization (manually via Dashboard or through admin portal during enterprise onboarding)
29
options.setLoginHint("user@example.com");
30
31
// redirectUrl: Your callback endpoint that receives the authorization code
32
// Must match the URL registered in your Scalekit dashboard
33
String redirectUrl = "https://your-app.com/auth/callback";
34
35
try {
36
String url = scalekitClient
37
.authentication()
38
.getAuthorizationUrl(redirectUrl, options)
39
.toString();
40
// Redirect user to this URL to begin SSO authentication
41
} catch (Exception e) {
42
System.out.println(e.getMessage());
43
}
44
}
45
}
```
* Direct URL (No SDK) OAuth2 authorization URL
```sh
/oauth/authorize?
response_type=code& # OAuth2 authorization code flow
client_id=& # Your Scalekit client ID
redirect_uri=& # URL-encoded callback URL
scope=openid profile email& # "offline_access" is required to receive a refresh token
organization_id=org_15421144869927830& # (Optional) Route by organization
connection_id=conn_15696105471768821& # (Optional) Specific SSO connection
login_hint=user@example.com # (Optional) Extract domain from email
```
**SSO identifiers** (choose one or more, evaluated in order of precedence): * `connection_id` - Direct to specific SSO connection (highest precedence) * `organization_id` - Route to organization’s SSO * `domain_hint` - Lookup connection by domain * `login_hint` - Extract domain from email (lowest precedence). Domain must be registered to the organization (manually via Dashboard or through admin portal when [onboarding an enterprise customer](/sso/guides/onboard-enterprise-customers/)) Example with actual values
```http
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_15421144869927830
```
Enterprise users see their identity provider’s login page. Users verify their identity through the authentication policies set by their organization’s administrator. Post successful verification, the user profile is [normalized](/sso/guides/user-profile-details/) and sent to your app. If your application needs to verify whether an SSO connection exists for a specific domain before proceeding, you can use the [list connections by domain SDK method](/guides/user-auth/check-sso-domain/). For details on how Scalekit determines which SSO connection to use, refer to the [SSO identifier precedence rules](/sso/guides/authorization-url/#parameter-precedence). 3. ## Get user details from the callback [Section titled “Get user details from the callback”](#get-user-details-from-the-callback) After successful authentication, Scalekit redirects to your callback URL with an authorization code. Your application exchanges this code for the user’s profile information and session tokens. 1. Add a callback endpoint in your application (typically `https://your-app.com/auth/callback`) 2. [Register](/guides/dashboard/redirects/#allowed-callback-urls) it in your Scalekit dashboard > Authentication > Redirect URLS > Allowed Callback URLs In authentication flow, Scalekit redirects to your callback URL with an authorization code. Your application exchanges this code for the user’s profile information. * Node.js Fetch user profile
```javascript
1
// Extract authentication parameters from the callback request
2
const {
3
code,
4
error,
5
error_description,
6
idp_initiated_login,
7
connection_id,
8
relay_state
9
} = req.query;
10
11
if (error) {
12
// Handle authentication errors returned from the identity provider
13
}
14
15
// Recommended: Process IdP-initiated login flows (when users start from their SSO portal)
16
17
const result = await scalekit.authenticateWithCode(code, redirectUri);
18
const userEmail = result.user.email;
19
20
// Create a session for the authenticated user and grant appropriate access permissions
```
* Python Fetch user profile
```py
1
# Extract authentication parameters from the callback request
2
code = request.args.get('code')
3
error = request.args.get('error')
4
error_description = request.args.get('error_description')
5
idp_initiated_login = request.args.get('idp_initiated_login')
6
connection_id = request.args.get('connection_id')
7
relay_state = request.args.get('relay_state')
8
9
if error:
10
raise Exception(error_description)
11
12
# Recommended: Process IdP-initiated login flows (when users start from their SSO portal)
13
14
result = scalekit.authenticate_with_code(code, '')
15
16
# Access normalized user profile information
17
user_email = result.user.email
18
19
# Create a session for the authenticated user and grant appropriate access permissions
```
* Go Fetch user profile
```go
1
// Extract authentication parameters from the callback request
2
code := r.URL.Query().Get("code")
3
error := r.URL.Query().Get("error")
4
errorDescription := r.URL.Query().Get("error_description")
5
idpInitiatedLogin := r.URL.Query().Get("idp_initiated_login")
6
connectionID := r.URL.Query().Get("connection_id")
7
relayState := r.URL.Query().Get("relay_state")
8
9
if error != "" {
10
// Handle authentication errors returned from the identity provider
11
}
12
13
// Recommended: Process IdP-initiated login flows (when users start from their SSO portal)
14
15
result, err := scalekitClient.AuthenticateWithCode(r.Context(), code, redirectUrl)
16
17
if err != nil {
18
// Handle token exchange or validation errors
19
}
20
21
// Access normalized user profile information
22
userEmail := result.User.Email
23
24
// Create a session for the authenticated user and grant appropriate access permissions
```
* Java Fetch user profile
```java
1
// Extract authentication parameters from the callback request
2
String code = request.getParameter("code");
3
String error = request.getParameter("error");
4
String errorDescription = request.getParameter("error_description");
5
String idpInitiatedLogin = request.getParameter("idp_initiated_login");
6
String connectionID = request.getParameter("connection_id");
7
String relayState = request.getParameter("relay_state");
8
9
if (error != null && !error.isEmpty()) {
10
// Handle authentication errors returned from the identity provider
11
return;
12
}
13
14
// Recommended: Process IdP-initiated login flows (when users start from their SSO portal)
15
16
try {
17
AuthenticationResponse result = scalekit.authentication().authenticateWithCode(code, redirectUrl);
18
String userEmail = result.getIdTokenClaims().getEmail();
19
20
// Create a session for the authenticated user and grant appropriate access permissions
21
} catch (Exception e) {
22
// Handle token exchange or validation errors
23
}
```
The `result` object * Node.js Validate tokens
```js
1
// Validate and decode the ID token from the authentication result
2
const idTokenClaims = await scalekit.validateToken(result.idToken);
3
4
// Validate and decode the access token
5
const accessTokenClaims = await scalekit.validateToken(result.accessToken);
```
* Python Validate tokens
```py
1
# Validate and decode the ID token from the authentication result
2
id_token_claims = scalekit_client.validate_token(result["id_token"])
3
4
# Validate and decode the access token
5
access_token_claims = scalekit_client.validate_token(result["access_token"])
```
* Go Validate tokens
```go
1
// Create a background context for the API call
2
ctx := context.Background()
3
4
// Validate and decode the access token (uses JWKS from the client)
5
accessTokenClaims, err := scalekitClient.GetAccessTokenClaims(ctx, result.AccessToken)
6
if err != nil {
7
// handle error
8
}
```
* Java Validate tokens
```java
1
// Validate and decode the ID token
2
Map idTokenClaims = scalekitClient.validateToken(result.getIdToken());
3
4
// Validate and decode the access token
5
Map accessTokenClaims = scalekitClient.validateToken(result.getAccessToken());
```
- Auth result
```js
1
{
2
user: {
3
email: 'john@example.com',
4
familyName: 'Doe',
5
givenName: 'John',
6
username: 'john@example.com',
7
id: 'conn_70087756662964366;dcc62570-6a5a-4819-b11b-d33d110c7716'
8
},
9
idToken: 'eyJhbGciOiJSU..bcLQ',
10
accessToken: 'eyJhbGciO..',
11
expiresIn: 899
12
}
```
- ID token (decoded)
```js
1
{
2
iss: '', // Issuer: Scalekit environment URL (must match your environment)
3
aud: [ 'skc_70087756327420046' ], // Audience: Your client ID (must match for validation)
4
azp: 'skc_70087756327420046', // Authorized party: Usually same as aud
5
sub: 'conn_70087756662964366;e964d135-35c7-4a13-a3b4-2579a1cdf4e6', // Subject: Connection ID and IdP user ID (SSO-specific format)
6
oid: 'org_70087756646187150', // Organization ID: User's organization
7
exp: 1758952038, // Expiration: Unix timestamp (validate token hasn't expired)
8
iat: 1758692838, // Issued at: Unix timestamp when token was issued
9
at_hash: 'yMGIBg7BkmIGgD6_dZPEGQ', // Access token hash: For token binding validation
10
c_hash: '4x7qsXnlRw6dRC6twnuENw', // Authorization code hash: For code binding validation
11
amr: [ 'conn_70087756662964366' ], // Authentication method reference: SSO connection ID used for authentication
12
email: 'john@example.com', // User's email address
13
preferred_username: 'john@example.com', // Preferred username (often same as email for SSO)
14
family_name: 'Doe', // User's last name
15
given_name: 'John', // User's first name
16
sid: 'ses_91646612652163629', // Session ID: Links token to user session
17
client_id: 'skc_70087756327420046' // Client ID: Your application identifier
18
}
```
- Access token (decoded)
```js
1
{
2
"iss": "", // Issuer: Scalekit environment URL (must match your environment)
3
"aud": ["skc_70087756327420046"], // Audience: Your client ID (must match for validation)
4
"sub": "conn_70087756662964366;dcc62570-6a5a-4819-b11b-d33d110c7716", // Subject: Connection ID and IdP user ID (SSO-specific format)
5
"exp": 1758693916, // Expiration: Unix timestamp (validate token hasn't expired)
6
"iat": 1758693016, // Issued at: Unix timestamp when token was issued
7
"nbf": 1758693016, // Not before: Unix timestamp (token valid from this time)
8
"jti": "tkn_91646913048216109", // JWT ID: Unique token identifier
9
"client_id": "skc_70087756327420046" // Client ID: Your application identifier
10
}
```
4. ## Handle IdP-initiated SSO Recommended [Section titled “Handle IdP-initiated SSO ”](#handle-idp-initiated-sso-) When users start the login process from their identity provider’s portal (rather than your application), this is called IdP-initiated SSO. Scalekit converts these requests to secure SP-initiated flows automatically. Your initiate login endpoint receives an `idp_initiated_login` JWT parameter containing the user’s organization and connection details. Decode this token and generate a new authorization URL to complete the authentication flow securely.
```sh
https://yourapp.com/login?idp_initiated_login=
```
Configure your initiate login endpoint in [Dashboard > Authentication > Redirects](/guides/dashboard/redirects/#initiate-login-url) * Node.js handle-idp-initiated.js
```javascript
1
// Your initiate login endpoint receives the IdP-initiated login token
2
const { idp_initiated_login, error, error_description } = req.query;
3
4
if (error) {
5
return res.status(400).json({ message: error_description });
6
}
7
8
// When users start login from their IdP portal, convert to SP-initiated flow
9
if (idp_initiated_login) {
10
// Decode the JWT to extract organization and connection information
11
const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login);
12
13
const options = {
14
connectionId: claims.connection_id, // Specific SSO connection
15
organizationId: claims.organization_id, // User's organization
16
loginHint: claims.login_hint, // User's email for context
17
state: claims.relay_state // Preserve state from IdP
18
};
19
20
// Generate authorization URL and redirect to complete authentication
21
const authorizationURL = scalekit.getAuthorizationUrl(
22
'https://your-app.com/auth/callback',
23
options
24
);
25
26
return res.redirect(authorizationURL);
27
}
```
* Python handle\_idp\_initiated.py
```python
1
# Your initiate login endpoint receives the IdP-initiated login token
2
idp_initiated_login = request.args.get('idp_initiated_login')
3
error = request.args.get('error')
4
error_description = request.args.get('error_description')
5
6
if error:
7
raise Exception(error_description)
8
9
# When users start login from their IdP portal, convert to SP-initiated flow
10
if idp_initiated_login:
11
# Decode the JWT to extract organization and connection information
12
claims = await scalekit.get_idp_initiated_login_claims(idp_initiated_login)
13
14
options = AuthorizationUrlOptions()
15
options.connection_id = claims.get('connection_id') # Specific SSO connection
16
options.organization_id = claims.get('organization_id') # User's organization
17
options.login_hint = claims.get('login_hint') # User's email for context
18
options.state = claims.get('relay_state') # Preserve state from IdP
19
20
# Generate authorization URL and redirect to complete authentication
21
authorization_url = scalekit.get_authorization_url(
22
redirect_uri='https://your-app.com/auth/callback',
23
options=options
24
)
25
26
return redirect(authorization_url)
```
* Go handle\_idp\_initiated.go
```go
1
// Your initiate login endpoint receives the IdP-initiated login token
2
idpInitiatedLogin := r.URL.Query().Get("idp_initiated_login")
3
errorDesc := r.URL.Query().Get("error_description")
4
5
if errorDesc != "" {
6
http.Error(w, errorDesc, http.StatusBadRequest)
7
return
8
}
9
10
// When users start login from their IdP portal, convert to SP-initiated flow
11
if idpInitiatedLogin != "" {
12
// Decode the JWT to extract organization and connection information
13
claims, err := scalekitClient.GetIdpInitiatedLoginClaims(r.Context(), idpInitiatedLogin)
14
if err != nil {
15
http.Error(w, err.Error(), http.StatusInternalServerError)
16
return
17
}
18
19
options := scalekit.AuthorizationUrlOptions{
20
ConnectionId: claims.ConnectionID, // Specific SSO connection
21
OrganizationId: claims.OrganizationID, // User's organization
22
LoginHint: claims.LoginHint, // User's email for context
23
}
24
25
// Generate authorization URL and redirect to complete authentication
26
authUrl, err := scalekitClient.GetAuthorizationUrl(
27
"https://your-app.com/auth/callback",
28
options
29
)
30
31
if err != nil {
32
http.Error(w, err.Error(), http.StatusInternalServerError)
33
return
34
}
35
36
http.Redirect(w, r, authUrl.String(), http.StatusFound)
37
}
```
* Java HandleIdpInitiated.java
```java
1
// Your initiate login endpoint receives the IdP-initiated login token
2
@GetMapping("/login")
3
public RedirectView handleInitiateLogin(
4
@RequestParam(required = false, name = "idp_initiated_login") String idpInitiatedLoginToken,
5
@RequestParam(required = false) String error,
6
@RequestParam(required = false, name = "error_description") String errorDescription,
7
HttpServletResponse response) throws IOException {
8
9
if (error != null) {
10
response.sendError(HttpStatus.BAD_REQUEST.value(), errorDescription);
11
return null;
12
}
13
14
// When users start login from their IdP portal, convert to SP-initiated flow
15
if (idpInitiatedLoginToken != null) {
16
// Decode the JWT to extract organization and connection information
17
IdpInitiatedLoginClaims claims = scalekit
18
.authentication()
19
.getIdpInitiatedLoginClaims(idpInitiatedLoginToken);
20
21
if (claims == null) {
22
response.sendError(HttpStatus.BAD_REQUEST.value(), "Invalid token");
23
return null;
24
}
25
26
AuthorizationUrlOptions options = new AuthorizationUrlOptions();
27
options.setConnectionId(claims.getConnectionID()); // Specific SSO connection
28
options.setOrganizationId(claims.getOrganizationID()); // User's organization
29
options.setLoginHint(claims.getLoginHint()); // User's email for context
30
31
// Generate authorization URL and redirect to complete authentication
32
String authUrl = scalekit
33
.authentication()
34
.getAuthorizationUrl("https://your-app.com/auth/callback", options)
35
.toString();
36
37
response.sendRedirect(authUrl);
38
return null;
39
}
40
41
return null;
42
}
```
This approach provides enhanced security by converting IdP-initiated requests to standard SP-initiated flows, protecting against SAML assertion theft and replay attacks. Learn more: [IdP-initiated SSO implementation guide](/sso/guides/idp-init-sso/) 5. ## Test your SSO integration [Section titled “Test your SSO integration”](#test-your-sso-integration) Validate your implementation using the **IdP Simulator** and **Test Organization** included in your development environment. Test all three scenarios before deploying to production. Your environment includes a pre-configured test organization (found in **Dashboard > Organizations**) with domains like `@example.com` and `@example.org` for testing. Pass one of the following connection selectors in your authorization URL: * Email address with `@example.com` or `@example.org` domain * Test organization’s connection ID * Organization ID This opens the SSO login page (IdP Simulator) that simulates your customer’s identity provider login experience.  For detailed testing instructions and scenarios, see our [Complete SSO testing guide](/sso/guides/test-sso/) 6. ## Set up SSO with your existing authentication system [Section titled “Set up SSO with your existing authentication system”](#set-up-sso-with-your-existing-authentication-system) Many applications already use an authentication provider such as Auth0, Firebase, or AWS Cognito. To enable single sign-on (SSO) using Scalekit, configure Scalekit to work with your current authentication provider. ### Auth0 Integrate Scalekit with Auth0 for enterprise SSO [Know more →](/guides/integrations/auth-systems/auth0) ### Firebase Auth Add enterprise authentication to Firebase projects [Know more →](/guides/integrations/auth-systems/firebase) ### AWS Cognito Configure Scalekit with AWS Cognito user pools [Know more →](/guides/integrations/auth-systems/aws-cognito) 7. ## Onboard enterprise customers [Section titled “Onboard enterprise customers”](#onboard-enterprise-customers) Enable SSO for your enterprise customers by creating an organization in Scalekit and providing them access to the Admin Portal. Your customers configure their identity provider settings themselves through a self-service portal. **Create an organization** for your customer in [Dashboard > Organizations](https://app.scalekit.com/organizations), then provide Admin Portal access using one of these methods: * Shareable link Generate a secure link your customer can use to access the Admin Portal: generate-portal-link.js
```javascript
// Generate a one-time Admin Portal link for your customer
const portalLink = await scalekit.organization.generatePortalLink(
'org_32656XXXXXX0438' // Your customer's organization ID
);
// Share this link with your customer's IT admin via email or messaging
// Example: '/magicLink/8930509d-68cf-4e2c-8c6d-94d2b5e2db43
console.log('Admin Portal URL:', portalLink.location);
```
Send this link to your customer’s IT administrator through email, Slack, or your preferred communication channel. They can configure their SSO connection without any developer involvement. * Embedded portal Embed the Admin Portal directly in your application using an iframe: embed-portal.js
```javascript
// Generate a secure portal link at runtime
const portalLink = await scalekit.organization.generatePortalLink(orgId);
// Return the link to your frontend to embed in an iframe
res.json({ portalUrl: portalLink.location });
```
admin-settings.html
```html
```
Customers configure SSO without leaving your application, maintaining a consistent user experience. Learn more: [Embedded Admin Portal guide](/guides/admin-portal/#embed-the-admin-portal) **Enable domain verification** for seamless user experience. Once your customer verifies their domain (e.g., `@megacorp.org`), users can sign in without selecting their organization. Scalekit automatically routes them to the correct identity provider based on their email domain. **Pre-check SSO availability** before redirecting users. This prevents failed redirects when a user’s domain doesn’t have SSO configured: * Node.js check-sso-availability.js
```javascript
1
// Extract domain from user's email address
2
const domain = email.split('@')[1].toLowerCase(); // e.g., "megacorp.org"
3
4
// Check if domain has an active SSO connection
5
const { connections } = await scalekit.connection.listConnectionsByDomain(domain);
6
7
if (connections.length > 0) {
8
// Domain has SSO configured - redirect to identity provider
9
const authUrl = scalekit.getAuthorizationUrl(redirectUri, {
10
domainHint: domain // Automatically routes to correct IdP
11
});
12
return res.redirect(authUrl);
13
} else {
14
// No SSO for this domain - show alternative login methods
15
return showPasswordlessLogin();
16
}
```
* Python check\_sso\_availability.py
```python
1
# Extract domain from user's email address
2
domain = email.split('@')[1].lower() # e.g., "megacorp.org"
3
4
# Check if domain has an active SSO connection
5
connections = scalekit_client.connection.list_connections_by_domain(domain=domain).connections
6
7
if len(connections) > 0:
8
# Domain has SSO configured - redirect to identity provider
9
options = AuthorizationUrlOptions()
10
options.domain_hint = domain # Automatically routes to correct IdP
11
12
auth_url = scalekit_client.get_authorization_url(
13
redirect_uri=redirect_uri,
14
options=options
15
)
16
return redirect(auth_url)
17
else:
18
# No SSO for this domain - show alternative login methods
19
return show_passwordless_login()
```
* Go check\_sso\_availability.go
```go
1
// Extract domain from user's email address
2
parts := strings.Split(email, "@")
3
domain := strings.ToLower(parts[1]) // e.g., "megacorp.org"
4
5
// Check if domain has an active SSO connection
6
connections, err := scalekitClient.Connections.ListConnectionsByDomain(domain)
7
if err != nil {
8
// Handle error
9
return err
10
}
11
12
if len(connections) > 0 {
13
// Domain has SSO configured - redirect to identity provider
14
options := scalekit.AuthorizationUrlOptions{
15
DomainHint: domain, // Automatically routes to correct IdP
16
}
17
18
authUrl, err := scalekitClient.GetAuthorizationUrl(redirectUri, options)
19
if err != nil {
20
return err
21
}
22
23
c.Redirect(http.StatusFound, authUrl.String())
24
} else {
25
// No SSO for this domain - show alternative login methods
26
return showPasswordlessLogin()
27
}
```
* Java CheckSsoAvailability.java
```java
1
// Extract domain from user's email address
2
String[] parts = email.split("@");
3
String domain = parts[1].toLowerCase(); // e.g., "megacorp.org"
4
5
// Check if domain has an active SSO connection
6
List connections = scalekitClient
7
.connections()
8
.listConnectionsByDomain(domain);
9
10
if (connections.size() > 0) {
11
// Domain has SSO configured - redirect to identity provider
12
AuthorizationUrlOptions options = new AuthorizationUrlOptions();
13
options.setDomainHint(domain); // Automatically routes to correct IdP
14
15
String authUrl = scalekitClient
16
.authentication()
17
.getAuthorizationUrl(redirectUri, options)
18
.toString();
19
20
return new RedirectView(authUrl);
21
} else {
22
// No SSO for this domain - show alternative login methods
23
return showPasswordlessLogin();
24
}
```
This check ensures users only see SSO options when available, improving the login experience and reducing confusion.
---
# DOCUMENT BOUNDARY
---
# Set up environment & SDK
> Create your account, install SDK, set up AI tools, and verify your setup to start building with Scalekit
Create a Scalekit account, install the SDK, configure your credentials, and verify the setup. This prepares your environment for adding authentication to your application. Before you begin, create a Scalekit account if you haven’t already. After creating your account, a Scalekit workspace is automatically set up for you with dedicated development and production environments. [Create a Scalekit account](https://app.scalekit.com/ws/signup) 1. ## Get your API credentials [Section titled “Get your API credentials”](#get-your-api-credentials) Scalekit uses the OAuth 2.0 client credentials flow for secure API authentication. Navigate to **Dashboard > Developers > Settings > API credentials** and copy these values: .env
```sh
SCALEKIT_ENVIRONMENT_URL= # Example: https://acme.scalekit.dev or https://auth.acme.com (if custom domain is set)
SCALEKIT_CLIENT_ID= # Example: skc_1234567890abcdef
SCALEKIT_CLIENT_SECRET= # Example: test_abcdef1234567890
```
Your workspace includes two environment URLs: Environment URLs
```md
https://{your-subdomain}.scalekit.dev (Development)
https://{your-subdomain}.scalekit.com (Production)
```
View your environment URLs in **Dashboard > Developers > Settings**. 2. ## Install and initialize the SDK [Section titled “Install and initialize the SDK”](#install-and-initialize-the-sdk) Choose your preferred language and install the Scalekit SDK: * Node.js
```bash
npm install @scalekit-sdk/node
```
* Python
```sh
pip install scalekit-sdk-python
```
* Go
```sh
go get -u github.com/scalekit-inc/scalekit-sdk-go
```
* Java
```groovy
/* Gradle users - add the following to your dependencies in build file */
implementation "com.scalekit:scalekit-sdk-java:2.1.3"
```
```xml
com.scalekit
scalekit-sdk-java
2.1.3
```
After installation, initialize the SDK with your credentials: * Node.js Initialize SDK
```js
1
import { Scalekit } from '@scalekit-sdk/node';
2
3
// Initialize the Scalekit client with your credentials
4
const scalekit = new Scalekit(
5
process.env.SCALEKIT_ENVIRONMENT_URL,
6
process.env.SCALEKIT_CLIENT_ID,
7
process.env.SCALEKIT_CLIENT_SECRET
8
);
```
* Python Initialize SDK
```python
1
from scalekit import ScalekitClient
2
import os
3
4
# Initialize the Scalekit client with your credentials
5
scalekit_client = ScalekitClient(
6
env_url=os.getenv('SCALEKIT_ENVIRONMENT_URL'),
7
client_id=os.getenv('SCALEKIT_CLIENT_ID'),
8
client_secret=os.getenv('SCALEKIT_CLIENT_SECRET')
9
)
```
* Go Initialize SDK
```go
1
import (
2
"os"
3
"github.com/scalekit-inc/scalekit-sdk-go"
4
)
5
6
// Initialize the Scalekit client with your credentials
7
scalekitClient := scalekit.NewScalekitClient(
8
os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
9
os.Getenv("SCALEKIT_CLIENT_ID"),
10
os.Getenv("SCALEKIT_CLIENT_SECRET"),
11
)
```
* Java Initialize SDK
```java
1
import com.scalekit.ScalekitClient;
2
3
// Initialize the Scalekit client with your credentials
4
ScalekitClient scalekitClient = new ScalekitClient(
5
System.getenv("SCALEKIT_ENVIRONMENT_URL"),
6
System.getenv("SCALEKIT_CLIENT_ID"),
7
System.getenv("SCALEKIT_CLIENT_SECRET")
8
);
```
3. ## Verify your setup [Section titled “Verify your setup”](#verify-your-setup) Test your configuration by listing organizations in your workspace. This confirms your credentials work correctly. * cURL Authenticate with client credentials
```bash
# Get an access token
curl https:///oauth/token \
-X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=' \
-d 'client_secret=' \
-d 'grant_type=client_credentials'
```
This returns an access token:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsImInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 86399,
"scope": "openid"
}
```
Use the token to access the Scalekit API List organizations
```sh
curl -L '/api/v1/organizations?page_size=5' \
-H 'Authorization: Bearer '
```
* Node.js Create a file `verify.js` with the following code: verify.js
```javascript
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 { organizations } = await scalekit.organization.listOrganization({
pageSize: 5,
});
console.log(`Name of the first organization: ${organizations[0].display_name}`);
```
Run the verification script: Run verification
```bash
node verify.js
```
* Python Create a file `verify.py` with the following code: verify.py
```python
from scalekit import ScalekitClient
import os
# Initialize the SDK client
scalekit_client = ScalekitClient(
os.getenv('SCALEKIT_ENVIRONMENT_URL'),
os.getenv('SCALEKIT_CLIENT_ID'),
os.getenv('SCALEKIT_CLIENT_SECRET')
)
org_list = scalekit_client.organization.list_organizations(page_size=5)
print(f'Name of the first organization: {org_list[0].display_name}')
```
Run the verification script: Run verification
```bash
python verify.py
```
* Go Create a file `verify.go` with the following code: verify.go
```go
package main
import (
"context"
"fmt"
"os"
"github.com/scalekit-inc/scalekit-sdk-go"
)
func main() {
ctx := context.Background()
scalekitClient := scalekit.NewScalekitClient(
os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
os.Getenv("SCALEKIT_CLIENT_ID"),
os.Getenv("SCALEKIT_CLIENT_SECRET"),
)
organizations, err := scalekitClient.Organization.ListOrganizations(ctx, &scalekit.ListOrganizationsParams{
PageSize: 5,
})
if err != nil {
panic(err)
}
fmt.Printf("Name of the first organization: %s\n", organizations[0].DisplayName)
}
```
* Java Create a file `Verify.java` with the following code: Verify.java
```java
import com.scalekit.ScalekitClient;
import com.scalekit.models.ListOrganizationsResponse;
public class Verify {
public static void main(String[] args) {
ScalekitClient scalekitClient = new ScalekitClient(
System.getenv("SCALEKIT_ENVIRONMENT_URL"),
System.getenv("SCALEKIT_CLIENT_ID"),
System.getenv("SCALEKIT_CLIENT_SECRET")
);
ListOrganizationsResponse organizations = scalekitClient.organizations().listOrganizations(5, "");
System.out.println("Name of the first organization: " + organizations.getOrganizations()[0].getDisplayName());
}
}
```
If you see organization data, your setup is complete! You’re now ready to implement authentication in your application. ## Set up Scalekit MCP Server Optional [Section titled “Set up Scalekit MCP Server ”](#set-up-scalekit-mcp-server-) Scalekit’s Model Context Protocol (MCP) server connects your AI coding assistants to Scalekit. Manage environments, organizations, users, and authentication through natural language queries in your MCP client. The MCP server provides AI assistants with tools for environment management, organization and user management, authentication connection setup, role administration, and admin portal access. It uses OAuth 2.1 authentication to securely connect your AI tools to your Scalekit workspace. ### Configure your MCP client [Section titled “Configure your MCP client”](#configure-your-mcp-client) Use the most common client configs below. For the full list of supported MCP hosts and editor setups, see the [Scalekit MCP server guide](/dev-kit/ai-assisted-development/scalekit-mcp-server/). * Claude Code Run this command in your terminal: Terminal
```bash
1
claude mcp add --transport http scalekit https://mcp.scalekit.com/
```
* Cursor Edit `~/.cursor/mcp.json`, or open **Cursor Settings → MCP → Add New Global MCP Server** and paste the config: \~/.cursor/mcp.json
```json
{
"mcpServers": {
"scalekit": {
"url": "https://mcp.scalekit.com/"
}
}
}
```
* Codex Run this command in your terminal: Terminal
```bash
1
codex mcp add scalekit --url https://mcp.scalekit.com/
```
* OpenCode Edit `opencode.json` in your project root: opencode.json
```json
{
"mcp": {
"scalekit": {
"type": "remote",
"url": "https://mcp.scalekit.com/"
}
}
}
```
After configuration, your MCP client will initiate an OAuth authorization workflow to securely connect to Scalekit’s MCP server. ## Configure code editors for Scalekit documentation [Section titled “Configure code editors for Scalekit documentation”](#configure-code-editors-for-scalekit-documentation) In-code editor chat features are powered by models that understand your codebase and project context. These models search the web for relevant information to help you. However, they may not always have the latest information. Follow the instructions below to configure your code editors to explicitly index for up-to-date information. ### Set up Cursor [Section titled “Set up Cursor”](#set-up-cursor) [Play](https://youtube.com/watch?v=oMMG1k_9fmU) To enable Cursor to access up-to-date Scalekit documentation: 1. Open Cursor settings (Cmd/Ctrl + ,) 2. Navigate to **Indexing & Docs** section 3. Click on **Add** 4. Add `https://docs.scalekit.com/llms-full.txt` to the indexable URLs 5. Click on **Save** Once configured, use `@Scalekit Docs` in your chat to ask questions about Scalekit features, APIs, and integration guides. Cursor will search the latest documentation to provide accurate, up-to-date answers. ### Use Windsurf [Section titled “Use Windsurf”](#use-windsurf)  Windsurf enables `@docs` mentions within the Cascade chat to search for the best answers to your questions. * Full Documentation
```plaintext
1
@docs:https://docs.scalekit.com/llms-full.txt
2
```
Costs more tokens. * Specific Section
```plaintext
1
@docs:https://docs.scalekit.com/your-specific-section-or-file
2
```
Costs less tokens. * Let AI decide
```plaintext
1
@docs:https://docs.scalekit.com/llms.txt
2
```
Costs tokens as per the model decisions. ## Use AI assistants [Section titled “Use AI assistants”](#use-ai-assistants) Assistants like **Anthropic Claude**, **Ollama**, **Google Gemini**, **Vercel v0**, **OpenAI’s ChatGPT**, or your own models can help you with Scalekit projects. [Play](https://youtube.com/watch?v=ZDAI32I6s-I)
---
# DOCUMENT BOUNDARY
---
# Initiate user signup or login
> Create authorization URLs and redirect users to Scalekit's hosted login page
Login initiation begins your authentication flow. You redirect users to Scalekit’s hosted login page by creating an authorization URL with appropriate parameters.When users visit this URL, Scalekit’s authorization server validates the request, displays the login interface, and handles authentication through your configured connection methods (SSO, social providers, Magic Link or Email OTP Authorization URL format
```sh
/oauth/authorize?
response_type=code& # always `code` for authorization code flow
client_id=& # Dashboard > Developers > Settings > API Credentials
redirect_uri=& # Dashboard > Authentication > Redirect URLs > Allowed Callback URLs
scope=openid+profile+email+offline_access& # Permissions requested. Include `offline_access` for refresh tokens
state= # prevent CSRF attacks
```
The authorization request includes several parameters that control authentication behavior: * **Required parameters** ensure Scalekit can identify your application and return the user securely * **Optional parameters** enable organization routing and pre-populate fields * **Security parameters** prevent unauthorized access attempts Understand each parameter and how it controls the authorization flow: | Query parameter | Description | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `response_type` | Set to `code` for authorization code flow Required Indicates the expected response type | | `client_id` | Your application’s public identifier from the dashboard Required Scalekit uses this to identify and validate your application | | `redirect_uri` | Your application’s callback URL where Scalekit returns the authorization code Required Must be registered in your dashboard settings | | `scope` | Space-separated list of permissions Required Always include `openid profile email`. Add `offline_access` to request refresh tokens for extended sessions | | `state` | Random string generated by your application Recommended Scalekit returns this unchanged. Use it to prevent CSRF attacks and maintain request state | | `prompt` | Value to control the authentication flow Optional Use `login` to force re-authentication even when the user has a valid session. Send it only when you need to re-verify identity — including it on routine logins re-prompts users unnecessarily, a common cause of users appearing logged out Use `create` to trigger the sign-up page Use `select_account` to force the organization selector, even if the user has an active session with an organization. The selector appears only when the user belongs to multiple organizations Use `none` to check silently whether a session exists, without showing the login or sign-up page | | `organization_id` | Skip routing the user to the hosted login page and route them to the enterprise SSO connection configured for the organization Optional | | `connection_id` | Skip routing the user to the hosted login page and route them to a specific enterprise SSO connection Optional | | `login_hint` | Used for [Home Realm Discovery](/authenticate/auth-methods/enterprise-sso/#identify-and-enforce-sso-for-organization-users). Scalekit extracts the email domain from `login_hint` and routes the user to the matching organization’s SSO connection based on configured domain rules Optional | | `provider` | Skip routing user to hosted login page and direct user to a specific social connection. Supported values: `google`, `microsoft`, `github`, `gitlab`, `linkedin`, and `salesforce` Optional | ## Set up login flow [Section titled “Set up login flow”](#set-up-login-flow) 1. #### Add `state` parameter recommended [Section titled “Add state parameter ”](#add-state-parameter-) Always generate a cryptographically secure random string for the `state` parameter and store it temporarily (session, local storage, cache, etc). This can be used to validate that the state value returned in the callback matches the original value you sent. This prevents **CSRF (Cross-Site Request Forgery)** attacks where an attacker tricks users into approving unauthorized authentication requests. * Node.js Generate and store state
```javascript
1
// Generate secure random state
2
const state = require('crypto').randomBytes(32).toString('hex');
3
// Store it temporarily (session, local storage, cache, etc)
4
sessionStorage.oauthState = state;
```
* Python Generate and store state
```python
1
import os
2
import secrets
3
4
# Generate secure random state
5
state = secrets.token_hex(32)
6
# Store it temporarily (session, local storage, cache, etc)
7
session['oauth_state'] = state
```
* Go Generate and store state
```go
1
import (
2
"crypto/rand"
3
"encoding/hex"
4
)
5
6
// Generate secure random state
7
b := make([]byte, 32)
8
rand.Read(b)
9
state := hex.EncodeToString(b)
10
// Store it temporarily (session, local storage, cache, etc)
11
// Example for Go: use a storage library
12
// session.Set("oauth_state", state)
```
* Java Generate and store state
```java
1
import java.security.SecureRandom;
2
import java.util.Base64;
3
4
// Generate secure random state
5
SecureRandom sr = new SecureRandom();
6
byte[] randomBytes = new byte[32];
7
sr.nextBytes(randomBytes);
8
String state = Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
9
// Store it temporarily (session, local storage, cache, etc)
10
// Example for Java: use any storage library
11
// session.setAttribute("oauth_state", state);
```
2. #### Redirect to the authorization URL [Section titled “Redirect to the authorization URL”](#redirect-to-the-authorization-url) Use the Scalekit SDK to generate the authorization URL. This method constructs the URL locally without making network requests. Redirect users to this URL to start authentication. * Node.js Express.js
```diff
1
import { Scalekit } from '@scalekit-sdk/node';
2
3
const scalekit = new Scalekit(/* your credentials */);
4
5
// Basic authorization URL for general login
6
const redirectUri = 'https://yourapp.com/auth/callback';
7
const options = {
8
scopes: ['openid', 'profile', 'email', 'offline_access'],
9
state: sessionStorage.oauthState,
10
};
11
12
const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options);
13
14
// Redirect user to Scalekit's hosted login page
15
res.redirect(authorizationUrl);
```
* Python Flask
```python
1
from scalekit import ScalekitClient, AuthorizationUrlOptions
2
3
scalekit = ScalekitClient(/* your credentials */)
4
5
# Basic authorization URL for general login
6
redirect_uri = 'https://yourapp.com/auth/callback'
7
options = AuthorizationUrlOptions()
8
options.scopes = ['openid', 'profile', 'email', 'offline_access']
9
options.state = session['oauth_state']
10
11
authorization_url = scalekit.get_authorization_url(redirect_uri, options)
12
13
# Redirect user to Scalekit's hosted login page
14
return redirect(authorization_url)
```
* Go Gin
```go
1
import "github.com/scalekit-inc/scalekit-sdk-go"
2
3
scalekit := scalekit.NewScalekitClient(/* your credentials */)
4
5
// Basic authorization URL for general login
6
redirectUri := "https://yourapp.com/auth/callback"
7
options := scalekit.AuthorizationUrlOptions{
8
Scopes: []string{"openid", "profile", "email", "offline_access"},
9
State: "your_generated_state", // Add this line
10
}
11
12
authorizationUrl, err := scalekitClient.GetAuthorizationUrl(redirectUri, options)
13
14
// Redirect user to Scalekit's hosted login page
15
c.Redirect(http.StatusFound, authorizationUrl.String())
```
* Java Spring
```java
1
import com.scalekit.ScalekitClient;
2
import com.scalekit.internal.http.AuthorizationUrlOptions;
3
4
ScalekitClient scalekit = new ScalekitClient(/* your credentials */);
5
6
// Basic authorization URL for general login
7
String redirectUri = "https://yourapp.com/auth/callback";
8
AuthorizationUrlOptions options = new AuthorizationUrlOptions();
9
options.setScopes(Arrays.asList("openid", "profile", "email", "offline_access"));
10
options.setState("your_generated_state"); // Add this line
11
12
URL authorizationUrl = scalekit.authentication().getAuthorizationUrl(redirectUri, options);
13
14
// Redirect user to Scalekit's hosted login page
15
return new RedirectView(authorizationUrl.toString());
```
Scalekit will try to verify the user’s identity and redirect them to your application’s callback URL. If the user is a new user, Scalekit will automatically create a new user account. ## Dedicated sign-up flow [Section titled “Dedicated sign-up flow”](#dedicated-sign-up-flow) To keep sign-up separate from login, use the `prompt: 'create'` parameter to send users straight to the dedicated sign-up page. * Node.js Express.js
```diff
1
const redirectUri = 'http://localhost:3000/api/callback';
2
const options = {
3
scopes: ['openid', 'profile', 'email', 'offline_access'],
4
prompt: 'create', // explicitly takes you to sign up flow
5
};
6
7
const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options);
8
9
res.redirect(authorizationUrl);
```
* Python Flask
```diff
1
from scalekit import AuthorizationUrlOptions
2
3
redirect_uri = 'http://localhost:3000/api/callback'
4
options = AuthorizationUrlOptions()
5
options.scopes=['openid', 'profile', 'email', 'offline_access']
6
options.prompt='create' # optional: explicitly takes you to sign up flow
7
8
authorization_url = scalekit.get_authorization_url(redirect_uri, options)
9
10
# For web frameworks like Flask/Django:
11
# return redirect(authorization_url)
```
* Go Gin
```diff
1
redirectUri := "http://localhost:3000/api/callback"
2
options := scalekit.AuthorizationUrlOptions{
3
Scopes: []string{"openid", "profile", "email", "offline_access"},
4
+Prompt: "create", // explicitly takes you to sign up flow
5
}
6
7
authorizationUrl, err := scalekitClient.GetAuthorizationUrl(redirectUri, options)
8
if err != nil {
9
// handle error appropriately
10
panic(err)
11
}
12
13
// For web frameworks like Gin:
14
// c.Redirect(http.StatusFound, authorizationUrl.String())
```
* Java Spring
```diff
1
import com.scalekit.internal.http.AuthorizationUrlOptions;
2
import java.net.URL;
3
import java.util.Arrays;
4
5
String redirectUri = "http://localhost:3000/api/callback";
6
AuthorizationUrlOptions options = new AuthorizationUrlOptions();
7
options.setScopes(Arrays.asList("openid", "profile", "email", "offline_access"));
8
+options.setPrompt("create");
9
10
URL authorizationUrl = scalekit.authentication().getAuthorizationUrl(redirectUri, options);
```
After the user authenticates either in signup or login flows: 1. Scalekit generates an authorization code 2. Makes a callback to your registered allowed callback URL 3. Your backend exchanges the code for tokens by making a server-to-server request This approach keeps sensitive operations server-side and protects your application’s credentials. Let’s take a look at how to complete the login in the next step.