> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# SSO (OIDC + SAML + LDAP)

> Enterprise OIDC and SAML single sign-on, plus the LDAP direct-bind preview, with multi-IdP connections and domain enforcement.

## Overview

The `sso` sub-client manages OIDC and SAML 2.0 enterprise connections,
domain-based enforcement, JIT provisioning, and sessions. It also exposes the
current LDAP direct-bind preview; that preview is not a general LDAP directory
integration and has the limitations documented below.

```typescript theme={null}
// Create an enterprise SSO connection
const conn = await grantex.sso.createConnection({
  name: 'Okta Production',
  protocol: 'oidc',
  issuerUrl: 'https://mycompany.okta.com',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  domains: ['mycompany.com'],
  jitProvisioning: true,
});
```

***

## Enterprise SSO Connections

### sso.createConnection()

Create a new SSO identity provider connection. You can create multiple connections for different domains or providers.

```typescript theme={null}
const conn = await grantex.sso.createConnection({
  name: 'Okta Production',
  protocol: 'oidc',
  issuerUrl: 'https://mycompany.okta.com',
  clientId: 'your-okta-client-id',
  clientSecret: 'your-okta-client-secret',
  domains: ['mycompany.com', 'mycompany.org'],
  jitProvisioning: true,
  defaultRole: 'member',
});

console.log(conn.id);              // 'sso_conn_01HX...'
console.log(conn.name);            // 'Okta Production'
console.log(conn.protocol);        // 'oidc'
console.log(conn.status);          // 'active'
console.log(conn.domains);         // ['mycompany.com', 'mycompany.org']
console.log(conn.jitProvisioning); // true
console.log(conn.createdAt);       // '2026-03-29T12:00:00Z'
```

#### SAML example

```typescript theme={null}
const samlConn = await grantex.sso.createConnection({
  name: 'Azure AD SAML',
  protocol: 'saml',
  metadataUrl: 'https://login.microsoftonline.com/.../federationmetadata/2007-06/federationmetadata.xml',
  assertionConsumerServiceUrl: 'https://yourapp.com/sso/saml/callback',
  entityId: 'https://yourapp.com/saml/metadata',
  domains: ['contoso.com'],
  jitProvisioning: true,
  attributeMapping: {
    email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
    name: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/displayname',
  },
});
```

#### Parameters

<ParamField body="name" type="string" required>
  A human-readable name for this connection (e.g. `"Okta Production"`).
</ParamField>

<ParamField body="protocol" type="'oidc' | 'saml' | 'ldap'" required>
  The SSO protocol. Use `"oidc"` for OpenID Connect, `"saml"` for SAML 2.0, or
  `"ldap"` for the current LDAP direct-bind preview.
</ParamField>

<ParamField body="issuerUrl" type="string">
  The OIDC issuer URL. Required when `protocol` is `"oidc"`.
</ParamField>

<ParamField body="clientId" type="string">
  OAuth 2.0 client ID from your identity provider. Required when `protocol` is `"oidc"`.
</ParamField>

<ParamField body="clientSecret" type="string">
  OAuth 2.0 client secret from your identity provider. Required when `protocol` is `"oidc"`.
</ParamField>

<ParamField body="metadataUrl" type="string">
  The SAML metadata URL. Required when `protocol` is `"saml"`.
</ParamField>

<ParamField body="assertionConsumerServiceUrl" type="string">
  The SAML ACS URL where the IdP posts assertions. Required when `protocol` is `"saml"`.
</ParamField>

<ParamField body="entityId" type="string">
  The SAML service provider entity ID. Required when `protocol` is `"saml"`.
</ParamField>

<ParamField body="domains" type="string[]">
  Email domains to associate with this connection. Users with matching email domains are automatically routed to this IdP.
</ParamField>

<ParamField body="jitProvisioning" type="boolean">
  Enable just-in-time user provisioning. When `true`, users are automatically created on first login. Defaults to `false`.
</ParamField>

<ParamField body="defaultRole" type="string">
  The default role assigned to JIT-provisioned users (e.g. `"member"`, `"admin"`).
</ParamField>

<ParamField body="attributeMapping" type="Record<string, string>">
  Custom attribute mapping for SAML assertions. Maps Grantex fields to IdP attribute names.
</ParamField>

#### Response: `SsoConnection`

<ResponseField name="id" type="string">
  Unique connection identifier (e.g. `sso_conn_01HX...`).
</ResponseField>

<ResponseField name="name" type="string">
  The connection display name.
</ResponseField>

<ResponseField name="protocol" type="'oidc' | 'saml' | 'ldap'">
  The SSO protocol.
</ResponseField>

<ResponseField name="status" type="'active' | 'inactive' | 'error'">
  Current connection status.
</ResponseField>

<ResponseField name="domains" type="string[]">
  Associated email domains.
</ResponseField>

<ResponseField name="jitProvisioning" type="boolean">
  Whether JIT provisioning is enabled.
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 creation timestamp.
</ResponseField>

<ResponseField name="updatedAt" type="string">
  ISO 8601 last-updated timestamp.
</ResponseField>

<Note>
  The `clientSecret` is never returned in responses. It is stored securely on the server.
</Note>

***

### sso.listConnections()

List all SSO connections for your organization.

```typescript theme={null}
const connections = await grantex.sso.listConnections();

for (const conn of connections.connections) {
  console.log(`${conn.name} (${conn.protocol}) - ${conn.status}`);
  console.log(`  Domains: ${conn.domains.join(', ')}`);
}
```

#### Response: `SsoConnectionList`

<ResponseField name="connections" type="SsoConnection[]">
  Array of SSO connection objects.
</ResponseField>

***

### sso.getConnection()

Retrieve a single SSO connection by ID.

```typescript theme={null}
const conn = await grantex.sso.getConnection('sso_conn_01HX...');

console.log(conn.name);     // 'Okta Production'
console.log(conn.protocol); // 'oidc'
console.log(conn.status);   // 'active'
```

#### Parameters

<ParamField body="connectionId" type="string" required>
  The connection ID to retrieve.
</ParamField>

#### Response

Returns an `SsoConnection` object.

***

### sso.updateConnection()

Update an existing SSO connection.

```typescript theme={null}
const updated = await grantex.sso.updateConnection('sso_conn_01HX...', {
  name: 'Okta Production (updated)',
  jitProvisioning: false,
  domains: ['mycompany.com', 'mycompany.org', 'subsidiary.com'],
});

console.log(updated.name);    // 'Okta Production (updated)'
console.log(updated.domains); // ['mycompany.com', 'mycompany.org', 'subsidiary.com']
```

#### Parameters

<ParamField body="connectionId" type="string" required>
  The connection ID to update.
</ParamField>

<ParamField body="name" type="string">
  Updated display name.
</ParamField>

<ParamField body="domains" type="string[]">
  Updated list of associated email domains.
</ParamField>

<ParamField body="jitProvisioning" type="boolean">
  Enable or disable JIT provisioning.
</ParamField>

<ParamField body="defaultRole" type="string">
  Updated default role for JIT-provisioned users.
</ParamField>

<ParamField body="attributeMapping" type="Record<string, string>">
  Updated SAML attribute mapping.
</ParamField>

#### Response

Returns the updated `SsoConnection` object.

***

### sso.deleteConnection()

Delete an SSO connection. Users associated with this connection will no longer be able to log in via SSO.

```typescript theme={null}
await grantex.sso.deleteConnection('sso_conn_01HX...');
// Returns void -- connection is removed
```

#### Parameters

<ParamField body="connectionId" type="string" required>
  The connection ID to delete.
</ParamField>

#### Response

Returns `void`.

<Warning>
  Deleting a connection immediately disables SSO login for all users routed through it. Ensure you have an alternative authentication method configured before removing a connection.
</Warning>

***

### sso.testConnection()

Test an SSO connection to verify that the IdP configuration is correct and reachable.

```typescript theme={null}
const test = await grantex.sso.testConnection('sso_conn_01HX...');

console.log(test.success);      // true
console.log(test.message);      // 'Connection verified successfully'
console.log(test.responseTime); // 142 (ms)
```

#### Parameters

<ParamField body="connectionId" type="string" required>
  The connection ID to test.
</ParamField>

#### Response: `SsoTestResult`

<ResponseField name="success" type="boolean">
  Whether the connection test passed.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable result message.
</ResponseField>

<ResponseField name="responseTime" type="number">
  IdP response time in milliseconds.
</ResponseField>

***

## Enforcement

### sso.setEnforcement()

Enforce SSO login for your organization. When enabled, all members must authenticate through an SSO connection.

```typescript theme={null}
const enforcement = await grantex.sso.setEnforcement({
  enforced: true,
  exemptRoles: ['owner'],
});

console.log(enforcement.enforced);    // true
console.log(enforcement.exemptRoles); // ['owner']
```

#### Parameters

<ParamField body="enforced" type="boolean" required>
  Whether SSO login is enforced for all organization members.
</ParamField>

<ParamField body="exemptRoles" type="string[]">
  Roles exempt from SSO enforcement (e.g. `["owner"]`). Exempt users can still log in with API keys.
</ParamField>

#### Response: `SsoEnforcement`

<ResponseField name="enforced" type="boolean">
  Whether SSO enforcement is active.
</ResponseField>

<ResponseField name="exemptRoles" type="string[]">
  Roles that are exempt from the enforcement policy.
</ResponseField>

***

## Session Management

### sso.listSessions()

List active SSO sessions for your organization.

```typescript theme={null}
const sessions = await grantex.sso.listSessions();

for (const session of sessions.sessions) {
  console.log(`${session.email} - ${session.connectionName} - expires ${session.expiresAt}`);
}
```

#### Response: `SsoSessionList`

<ResponseField name="sessions" type="SsoSession[]">
  Array of active SSO session objects.
</ResponseField>

Each `SsoSession` contains:

<ResponseField name="id" type="string">
  Session identifier.
</ResponseField>

<ResponseField name="email" type="string">
  The user's email address.
</ResponseField>

<ResponseField name="connectionId" type="string">
  The SSO connection used for this session.
</ResponseField>

<ResponseField name="connectionName" type="string">
  Display name of the SSO connection.
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 session creation timestamp.
</ResponseField>

<ResponseField name="expiresAt" type="string">
  ISO 8601 session expiration timestamp.
</ResponseField>

***

### sso.revokeSession()

Revoke an active SSO session, forcing the user to re-authenticate.

```typescript theme={null}
await grantex.sso.revokeSession('sso_sess_01HX...');
// Returns void -- session is revoked
```

#### Parameters

<ParamField body="sessionId" type="string" required>
  The session ID to revoke.
</ParamField>

#### Response

Returns `void`.

***

## Enterprise Login Flow

### sso.getLoginUrl() (enterprise)

Get the SSO authorization URL for a user based on their email domain. The domain is matched against configured connections to route the user to the correct IdP.

```typescript theme={null}
const login = await grantex.sso.getLoginUrl({
  domain: 'mycompany.com',
  redirectUri: 'https://yourapp.com/sso/callback',
});

console.log(login.authorizeUrl);
// → 'https://mycompany.okta.com/oauth2/v1/authorize?client_id=...&redirect_uri=...'
console.log(login.connectionId);  // 'sso_conn_01HX...'
console.log(login.protocol);     // 'oidc'
// Redirect the user to login.authorizeUrl
```

#### Parameters

<ParamField body="domain" type="string" required>
  The email domain to match against configured SSO connections (e.g. `"mycompany.com"`).
</ParamField>

<ParamField body="redirectUri" type="string">
  Override the redirect URI for this login request.
</ParamField>

#### Response: `SsoLoginResponse`

<ResponseField name="authorizeUrl" type="string">
  The full authorization URL. Redirect the user here.
</ResponseField>

<ResponseField name="connectionId" type="string">
  The matched SSO connection ID.
</ResponseField>

<ResponseField name="protocol" type="'oidc' | 'saml' | 'ldap'">
  The protocol of the matched connection.
</ResponseField>

***

### sso.handleOidcCallback()

Handle the callback from an OIDC identity provider. Exchanges the authorization code for user information and provisions the user if JIT is enabled.

```typescript theme={null}
const result = await grantex.sso.handleOidcCallback({
  code: 'oidc_auth_code',
  state: 'csrf_state',
});

console.log(result.email);        // 'alice@mycompany.com'
console.log(result.name);         // 'Alice Smith'
console.log(result.sub);          // 'okta|abc123'
console.log(result.developerId);  // 'dev_01HXYZ...'
console.log(result.connectionId); // 'sso_conn_01HX...'
console.log(result.provisioned);  // true (if JIT-created)
```

#### Parameters

<ParamField body="code" type="string" required>
  The authorization code from the OIDC callback.
</ParamField>

<ParamField body="state" type="string" required>
  The state parameter from the callback (used for CSRF protection).
</ParamField>

#### Response: `SsoCallbackResponse`

<ResponseField name="email" type="string | null">
  The user's email address from the IdP.
</ResponseField>

<ResponseField name="name" type="string | null">
  The user's display name from the IdP.
</ResponseField>

<ResponseField name="sub" type="string | null">
  The user's subject identifier from the IdP.
</ResponseField>

<ResponseField name="developerId" type="string">
  The Grantex developer ID that the user has been mapped to.
</ResponseField>

<ResponseField name="connectionId" type="string">
  The SSO connection that handled this authentication.
</ResponseField>

<ResponseField name="provisioned" type="boolean">
  Whether the user was just-in-time provisioned during this login.
</ResponseField>

***

### sso.handleSamlCallback()

Handle the callback from a SAML 2.0 identity provider. Validates the SAML assertion and provisions the user if JIT is enabled.

```typescript theme={null}
const result = await grantex.sso.handleSamlCallback({
  samlResponse: req.body.SAMLResponse,
  relayState: req.body.RelayState,
});

console.log(result.email);        // 'bob@contoso.com'
console.log(result.name);         // 'Bob Jones'
console.log(result.developerId);  // 'dev_01HXYZ...'
console.log(result.connectionId); // 'sso_conn_02HX...'
console.log(result.provisioned);  // false
```

#### Parameters

<ParamField body="samlResponse" type="string" required>
  The base64-encoded SAML response from the IdP.
</ParamField>

<ParamField body="relayState" type="string">
  The RelayState parameter from the SAML callback.
</ParamField>

#### Response

Returns the same `SsoCallbackResponse` as `handleOidcCallback()`.

***

### sso.handleLdapCallback()

Authenticate a user with the LDAP direct-bind preview. Unlike OIDC and SAML,
credentials are submitted directly. The built-in client binds a service account,
constructs a user DN from `ldapSearchFilter` plus `ldapSearchBase`, and binds that
DN with the submitted password. It does not search entries, read attributes, or
retrieve group memberships.

```typescript theme={null}
const result = await grantex.sso.handleLdapCallback({
  username: 'alice',
  password: 'user-password',
  connectionId: 'sso_conn_03HX...',
  org: 'org_01HXYZ...',
});

console.log(result.email);        // null
console.log(result.name);         // null
console.log(result.groups);       // []
console.log(result.mappedScopes); // defaultScopes only
console.log(result.developerId);  // 'dev_01HXYZ...'
console.log(result.sessionId);    // 'ssosess_01HXYZ...'
```

#### Parameters

<ParamField body="username" type="string" required>
  The username interpolated into the configured LDAP filter to construct the
  user DN. The built-in preview does not perform a directory search.
</ParamField>

<ParamField body="password" type="string" required>
  The user's LDAP password for bind authentication.
</ParamField>

<ParamField body="connectionId" type="string" required>
  The SSO connection ID for the LDAP directory.
</ParamField>

<ParamField body="org" type="string" required>
  The organization/developer ID that owns the LDAP connection.
</ParamField>

#### Response

Returns `SsoCallbackResult`. With the built-in LDAP client, `groups` is empty,
`name` and `email` are `null`, and `mappedScopes` therefore contains only
configured default scopes.

<Note>
  LDAP credentials are never stored by Grantex. They are used only for the bind
  operations and discarded after authentication. LDAP search, attribute
  retrieval, group lookup/mapping, and broad provider compatibility are not
  supported by the built-in preview client.
</Note>

***

## Full Enterprise SSO Flow Example

```typescript theme={null}
import { Grantex } from '@grantex/sdk';
import express from 'express';

const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY });
const app = express();
app.use(express.urlencoded({ extended: true }));

// Step 1: Create SSO connections (one-time setup)
await grantex.sso.createConnection({
  name: 'Okta Production',
  protocol: 'oidc',
  issuerUrl: 'https://mycompany.okta.com',
  clientId: process.env.OKTA_CLIENT_ID,
  clientSecret: process.env.OKTA_CLIENT_SECRET,
  domains: ['mycompany.com'],
  jitProvisioning: true,
  defaultRole: 'member',
});

await grantex.sso.createConnection({
  name: 'Azure AD SAML',
  protocol: 'saml',
  metadataUrl: process.env.AZURE_METADATA_URL,
  assertionConsumerServiceUrl: 'https://yourapp.com/sso/saml/callback',
  entityId: 'https://yourapp.com/saml/metadata',
  domains: ['contoso.com'],
  jitProvisioning: true,
});

// Step 2: Enforce SSO for the organization
await grantex.sso.setEnforcement({
  enforced: true,
  exemptRoles: ['owner'],
});

// Step 3: Redirect user to SSO login based on email domain
app.get('/sso/login', async (req, res) => {
  const { domain } = req.query;
  const login = await grantex.sso.getLoginUrl({
    domain: domain as string,
    redirectUri: 'https://yourapp.com/sso/callback',
  });
  res.redirect(login.authorizeUrl);
});

// Step 4a: Handle OIDC callback
app.get('/sso/callback', async (req, res) => {
  const { code, state } = req.query;
  const result = await grantex.sso.handleOidcCallback({
    code: code as string,
    state: state as string,
  });

  console.log(`Welcome, ${result.name} (${result.email})`);
  if (result.provisioned) {
    console.log('New user provisioned via JIT');
  }
  res.redirect('/dashboard');
});

// Step 4b: Handle SAML callback
app.post('/sso/saml/callback', async (req, res) => {
  const result = await grantex.sso.handleSamlCallback({
    samlResponse: req.body.SAMLResponse,
    relayState: req.body.RelayState,
  });

  console.log(`Welcome, ${result.name} (${result.email})`);
  res.redirect('/dashboard');
});

// Admin: List active sessions
app.get('/admin/sso/sessions', async (req, res) => {
  const sessions = await grantex.sso.listSessions();
  res.json(sessions);
});
```

***

## Legacy Single-Config Methods

<Note>
  The following methods manage a single OIDC configuration per organization. They are retained for backward compatibility. For new integrations, use the enterprise connection methods above which support multiple IdPs, SAML, and domain-based routing.
</Note>

### sso.createConfig()

Create or update the OIDC SSO configuration for your organization.

```typescript theme={null}
const config = await grantex.sso.createConfig({
  issuerUrl: 'https://accounts.google.com',
  clientId: 'your-google-client-id',
  clientSecret: 'your-google-client-secret',
  redirectUri: 'https://yourapp.com/sso/callback',
});

console.log(config.issuerUrl);   // 'https://accounts.google.com'
console.log(config.clientId);    // 'your-google-client-id'
console.log(config.redirectUri); // 'https://yourapp.com/sso/callback'
console.log(config.createdAt);   // '2026-02-28T12:00:00Z'
console.log(config.updatedAt);   // '2026-02-28T12:00:00Z'
```

#### Parameters

<ParamField body="issuerUrl" type="string" required>
  The OIDC issuer URL (e.g. `https://accounts.google.com`).
</ParamField>

<ParamField body="clientId" type="string" required>
  OAuth 2.0 client ID from your identity provider.
</ParamField>

<ParamField body="clientSecret" type="string" required>
  OAuth 2.0 client secret from your identity provider.
</ParamField>

<ParamField body="redirectUri" type="string" required>
  The callback URL that your IdP redirects to after authentication.
</ParamField>

#### Response: `SsoConfig`

<ResponseField name="issuerUrl" type="string">
  The OIDC issuer URL.
</ResponseField>

<ResponseField name="clientId" type="string">
  The OAuth 2.0 client ID.
</ResponseField>

<ResponseField name="redirectUri" type="string">
  The configured callback URL.
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 creation timestamp.
</ResponseField>

<ResponseField name="updatedAt" type="string">
  ISO 8601 last-updated timestamp.
</ResponseField>

<Note>
  The `clientSecret` is never returned in responses. It is stored securely on the server.
</Note>

***

### sso.getConfig()

Retrieve the current SSO configuration (without the client secret).

```typescript theme={null}
const config = await grantex.sso.getConfig();

console.log(config.issuerUrl);   // 'https://accounts.google.com'
console.log(config.clientId);    // 'your-google-client-id'
console.log(config.redirectUri); // 'https://yourapp.com/sso/callback'
```

#### Response

Returns an `SsoConfig` object.

***

### sso.deleteConfig()

Remove the SSO configuration. After deletion, SSO login is disabled for the organization.

```typescript theme={null}
await grantex.sso.deleteConfig();
// Returns void -- SSO is now disabled
```

#### Response

Returns `void`.

***

### sso.getLoginUrl() (legacy)

Get the OIDC authorization URL to redirect the user to for SSO login.

```typescript theme={null}
const login = await grantex.sso.getLoginUrl('dev_01HXYZ...');

console.log(login.authorizeUrl);
// → 'https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&state=...'
// Redirect the user to this URL
```

#### Parameters

<ParamField body="org" type="string" required>
  The developer ID of the organization initiating the SSO login.
</ParamField>

#### Response: `SsoLoginResponse`

<ResponseField name="authorizeUrl" type="string">
  The full OIDC authorization URL. Redirect the user here.
</ResponseField>

***

### sso.handleCallback()

Exchange the OIDC authorization code for user information after the identity provider redirects back.

```typescript theme={null}
const result = await grantex.sso.handleCallback(code, state);

console.log(result.email);       // 'alice@example.com'
console.log(result.name);        // 'Alice Smith'
console.log(result.sub);         // 'google-oauth2|12345'
console.log(result.developerId); // 'dev_01HXYZ...'
```

#### Parameters

<ParamField body="code" type="string" required>
  The authorization code from the IdP callback.
</ParamField>

<ParamField body="state" type="string" required>
  The state parameter from the IdP callback (used for CSRF protection).
</ParamField>

#### Response: `SsoCallbackResponse`

<ResponseField name="email" type="string | null">
  The user's email address from the IdP.
</ResponseField>

<ResponseField name="name" type="string | null">
  The user's display name from the IdP.
</ResponseField>

<ResponseField name="sub" type="string | null">
  The user's subject identifier from the IdP.
</ResponseField>

<ResponseField name="developerId" type="string">
  The Grantex developer ID that the user has been mapped to.
</ResponseField>

***

### Legacy SSO Flow Example

```typescript theme={null}
import { Grantex } from '@grantex/sdk';
import express from 'express';

const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY });
const app = express();

// Step 1: Configure SSO (one-time setup)
await grantex.sso.createConfig({
  issuerUrl: 'https://accounts.google.com',
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  redirectUri: 'https://yourapp.com/sso/callback',
});

// Step 2: Redirect user to SSO login
app.get('/sso/login', async (req, res) => {
  const { authorizeUrl } = await grantex.sso.getLoginUrl('dev_01HXYZ...');
  res.redirect(authorizeUrl);
});

// Step 3: Handle the callback
app.get('/sso/callback', async (req, res) => {
  const { code, state } = req.query;
  const result = await grantex.sso.handleCallback(code as string, state as string);

  // User is authenticated
  console.log(`Welcome, ${result.name} (${result.email})`);
  // Create a session, redirect to dashboard, etc.
  res.redirect('/dashboard');
});
```
