> ## 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.

# Enterprise SSO

> Set up OIDC and SAML single sign-on, plus the current LDAP direct-bind preview, with multi-IdP routing and JIT provisioning.

## Overview

Grantex supports **OIDC** and **SAML 2.0** enterprise SSO, plus an **LDAP
direct-bind preview**. You can configure multiple identity provider connections
per organization, route users by email domain, and map OIDC/SAML groups to
Grantex scopes. The built-in LDAP preview has the limitations described below.

**Key capabilities:**

| Feature               | Description                                                                                                     |
| --------------------- | --------------------------------------------------------------------------------------------------------------- |
| Multi-IdP connections | Configure multiple OIDC and SAML providers, plus LDAP direct-bind preview connections, per org                  |
| OIDC Discovery        | Automatic endpoint discovery via `.well-known/openid-configuration`                                             |
| ID token verification | Cryptographic JWT signature verification via JWKS                                                               |
| SAML 2.0              | Response parsing with X.509 certificate-based signature verification                                            |
| LDAP preview          | Service-account bind followed by a user bind to a constructed DN; no directory search or provider certification |
| Domain routing        | Route users to the correct IdP based on email domain                                                            |
| JIT provisioning      | Auto-create principals on first SSO login                                                                       |
| Group mapping         | Map OIDC/SAML groups or roles to Grantex scopes; the default LDAP client does not retrieve groups               |
| SSO enforcement       | Require SSO for all users in an organization                                                                    |
| Session management    | Track, list, and revoke active SSO sessions                                                                     |

***

## Setting up OIDC

### Step 1: Register Grantex in your IdP

In your identity provider (Okta, Azure AD, Google Workspace, Auth0, etc.), create a new application:

* **Application type:** Web application
* **Sign-in redirect URI:** `https://yourapp.com/sso/callback/oidc`
* **Scopes:** `openid email profile`
* **Grant type:** Authorization Code

Note the **Client ID**, **Client Secret**, and **Issuer URL**.

### Step 2: Create the SSO connection

```typescript theme={null}
const connection = 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,
  groupAttribute: 'groups',
  groupMappings: {
    Engineering: ['read', 'write', 'deploy'],
    Admins: ['admin', 'read', 'write'],
  },
  defaultScopes: ['read'],
});
```

### Step 3: Test the connection

```typescript theme={null}
const result = await grantex.sso.testConnection(connection.id);
// result.success === true
// result.issuer, result.authorizationEndpoint, result.tokenEndpoint, result.jwksUri
```

### Step 4: Handle the login flow

```typescript theme={null}
// Get the authorize URL (domain-based routing)
const { authorizeUrl } = await grantex.sso.getLoginUrl('dev_01HXYZ...', 'mycompany.com');
// Redirect user to authorizeUrl

// After IdP redirects back, handle the callback
const result = await grantex.sso.handleOidcCallback({
  code: req.query.code,
  state: req.query.state,
  redirect_uri: 'https://yourapp.com/sso/callback/oidc',
});

console.log(result.email);        // 'alice@mycompany.com'
console.log(result.mappedScopes); // ['read', 'write', 'deploy']
console.log(result.sessionId);    // 'ssosess_01HXYZ...'
console.log(result.principalId);  // 'scimuser_01HXYZ...' (JIT-provisioned)
```

***

## Setting up SAML 2.0

### Step 1: Configure your IdP

In your SAML identity provider:

* **SP Entity ID:** `urn:grantex:yourorg`
* **ACS URL:** `https://yourapp.com/sso/callback/saml`
* **NameID format:** Email address or persistent identifier
* **Attributes:** Map `email`, `displayName`, and `groups`

Download the **IdP certificate** and note the **SSO URL** and **Entity ID**.

### Step 2: Create the SAML connection

```typescript theme={null}
const connection = await grantex.sso.createConnection({
  name: 'Azure AD SAML',
  protocol: 'saml',
  idpEntityId: 'https://sts.windows.net/tenant-id/',
  idpSsoUrl: 'https://login.microsoftonline.com/tenant-id/saml2',
  idpCertificate: '-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----',
  spEntityId: 'urn:grantex:yourorg',
  spAcsUrl: 'https://yourapp.com/sso/callback/saml',
  domains: ['yourorg.com'],
  jitProvisioning: true,
  groupMappings: {
    SecurityTeam: ['admin', 'audit'],
    Developers: ['read', 'write'],
  },
});
```

### Step 3: Handle the SAML callback

```typescript theme={null}
// After the IdP POSTs the SAML response:
const result = await grantex.sso.handleSamlCallback({
  SAMLResponse: req.body.SAMLResponse,
  RelayState: req.body.RelayState,
});
```

***

## Setting up LDAP

LDAP authentication differs from OIDC and SAML in that there is no browser
redirect. The user submits credentials directly to your application, and
Grantex performs a service-account bind followed by a user bind.

<Warning>
  LDAP support is currently a direct-bind preview. The built-in client does not
  search for user entries, read directory attributes, or retrieve group
  memberships. It constructs the user DN from `ldapSearchFilter` and
  `ldapSearchBase`, returns no email or display name, and returns an empty groups
  list. Validate the exact DN convention against a staging directory before
  enabling it.
</Warning>

### Step 1: Gather LDAP server details

You will need the following from your directory administrator:

* **LDAP URL** — The server address (e.g. `ldaps://ldap.mycompany.com:636` for TLS or `ldap://ldap.mycompany.com:389`)
* **Bind DN** — The service-account distinguished name used for the initial connectivity/authentication bind (e.g. `cn=readonly,dc=mycompany,dc=com`)
* **Bind password** — The password for the bind DN
* **Search base** — The base DN appended to the constructed user RDN (e.g. `ou=people,dc=mycompany,dc=com`)
* **Search filter** — A template such as `(uid={{username}})`. In the preview client this is not executed as a search; parentheses are removed and the result is used as the user RDN.

<Warning>
  Always use `ldaps://` (LDAP over TLS) in production. Unencrypted LDAP transmits passwords in plaintext.
</Warning>

### Step 2: Create the LDAP connection

```typescript theme={null}
const connection = await grantex.sso.createConnection({
  name: 'Corporate LDAP',
  protocol: 'ldap',
  ldapUrl: 'ldaps://ldap.mycompany.com:636',
  ldapBindDn: 'cn=readonly,dc=mycompany,dc=com',
  ldapBindPassword: 'bind-password',
  ldapSearchBase: 'ou=people,dc=mycompany,dc=com',
  ldapSearchFilter: '(uid={{username}})', // -> uid=alice,ou=people,...
  domains: ['mycompany.com'],
  jitProvisioning: true,
  defaultScopes: ['read'],
});
```

### Step 3: Handle LDAP authentication

Since LDAP does not use browser redirects, the user submits credentials directly to your application. Your backend then calls the LDAP callback endpoint:

```typescript theme={null}
// User submits username/password via your login form
const result = await grantex.sso.handleLdapCallback({
  username: req.body.username,
  password: req.body.password,
  connectionId: connection.id,
  org: 'org_01HXYZ...',
});

console.log(result.email);        // null (attributes are not retrieved)
console.log(result.name);         // null (attributes are not retrieved)
console.log(result.groups);       // []
console.log(result.mappedScopes); // ['read'] (defaultScopes)
console.log(result.sessionId);    // 'ssosess_01HXYZ...'
console.log(result.principalId);  // JIT principal ID, when enabled
```

<Note>
  The preview performs two binds, not a directory search. It first binds the
  configured service account, constructs the user DN, then binds that DN with
  the submitted password. LDAP group-search settings are stored but are not
  used by the built-in client; only `defaultScopes` apply to its empty group list.
</Note>

***

## Domain-based routing

When you assign domains to connections, Grantex automatically routes users to the correct IdP:

```typescript theme={null}
// Connection A: domains ['engineering.co']  -> Okta OIDC
// Connection B: domains ['marketing.co']    -> Azure AD SAML

// User with @engineering.co email -> routed to Okta
const login = await grantex.sso.getLoginUrl('dev_01HXYZ...', 'engineering.co');
// login.protocol === 'oidc', login.connectionId === Connection A's ID

// User with @marketing.co email -> routed to Azure AD
const login2 = await grantex.sso.getLoginUrl('dev_01HXYZ...', 'marketing.co');
// login2.protocol === 'saml', login2.connectionId === Connection B's ID
```

***

## Group-to-scope mapping

Map identity provider groups to Grantex scopes:

```typescript theme={null}
await grantex.sso.createConnection({
  // ...
  groupAttribute: 'groups',    // OIDC claim name or SAML attribute name
  groupMappings: {
    'Engineering':    ['read', 'write', 'deploy'],
    'Administrators': ['admin', 'read', 'write', 'delete'],
    'ReadOnly':       ['read'],
  },
  defaultScopes: ['read'],  // Fallback when no groups match
});
```

When a user logs in with the groups `['Engineering', 'ReadOnly']`, they receive the union of matched scopes: `['read', 'write', 'deploy']`.

***

## JIT provisioning

When `jitProvisioning` is enabled, Grantex automatically creates or updates a principal (SCIM user) on each SSO login. This eliminates the need for manual user provisioning:

1. User authenticates via SSO
2. Grantex checks if a SCIM user exists with the IdP's subject identifier
3. If not found, creates a new SCIM user with the IdP's email and display name
4. If found, updates the existing user's details
5. Returns the `principalId` in the callback result

***

## SSO enforcement

Require all users in your organization to authenticate via SSO:

```typescript theme={null}
// Enable SSO enforcement
await grantex.sso.setEnforcement({ enforce: true });

// Disable SSO enforcement
await grantex.sso.setEnforcement({ enforce: false });
```

<Warning>
  Enabling SSO enforcement marks all active connections as enforced. Ensure you have at least one active, tested SSO connection before enabling.
</Warning>

***

## Session management

Track and manage active SSO sessions:

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

for (const session of sessions) {
  console.log(session.email, session.groups, session.expiresAt);
}

// Revoke a specific session
await grantex.sso.revokeSession(sessions[0].id);
```

Sessions expire after 8 hours by default.

***

## CLI reference

```bash theme={null}
# Create an OIDC connection
grantex sso connections create --name "Okta" --protocol oidc \
  --issuer-url https://mycompany.okta.com \
  --client-id $CLIENT_ID --client-secret $CLIENT_SECRET \
  --domains mycompany.com --jit-provisioning

# Create a SAML connection
grantex sso connections create --name "Azure AD" --protocol saml \
  --idp-entity-id "https://sts.windows.net/tenant-id/" \
  --idp-sso-url "https://login.microsoftonline.com/tenant-id/saml2" \
  --idp-certificate "$(cat idp-cert.pem)" \
  --sp-entity-id "urn:grantex:mycompany" \
  --sp-acs-url "https://myapp.com/sso/callback/saml"

# Create an LDAP connection
grantex sso connections create --name "Corporate LDAP" --protocol ldap \
  --ldap-url ldaps://ldap.mycompany.com:636 \
  --ldap-bind-dn "cn=readonly,dc=mycompany,dc=com" \
  --ldap-bind-password "$LDAP_BIND_PW" \
  --ldap-search-base "ou=people,dc=mycompany,dc=com" \
  --ldap-search-filter "(uid={{username}})" \
  --domains mycompany.com --jit-provisioning

# List all connections
grantex sso connections list

# Test a connection
grantex sso connections test sso_01HXYZ...

# Enable SSO enforcement
grantex sso enforce --enable

# List active sessions
grantex sso sessions list

# Revoke a session
grantex sso sessions revoke ssosess_01HXYZ...
```

***

## Supported identity providers

| Provider                         | OIDC                         | SAML 2.0                       |
| -------------------------------- | ---------------------------- | ------------------------------ |
| Okta                             | Yes                          | Yes                            |
| Azure AD / Entra ID              | Yes                          | Yes                            |
| Google Workspace                 | Yes                          | Yes                            |
| Auth0                            | Yes                          | Yes                            |
| OneLogin                         | Yes                          | Yes                            |
| PingFederate                     | Yes                          | Yes                            |
| JumpCloud                        | Yes                          | Yes                            |
| Any standards-compliant provider | OIDC discovery/JWKS required | Signed SAML responses required |

LDAP provider compatibility is not currently certified. The preview works only
when the configured filter and base construct the directory's exact user DN;
test the intended server and schema before production use.

***

## API endpoints

| Endpoint                       | Method | Description                                             |
| ------------------------------ | ------ | ------------------------------------------------------- |
| `/v1/sso/connections`          | POST   | Create SSO connection                                   |
| `/v1/sso/connections`          | GET    | List SSO connections                                    |
| `/v1/sso/connections/:id`      | GET    | Get SSO connection                                      |
| `/v1/sso/connections/:id`      | PATCH  | Update SSO connection                                   |
| `/v1/sso/connections/:id`      | DELETE | Delete SSO connection                                   |
| `/v1/sso/connections/:id/test` | POST   | Test SSO connection                                     |
| `/v1/sso/enforce`              | POST   | Set SSO enforcement                                     |
| `/v1/sso/sessions`             | GET    | List SSO sessions                                       |
| `/v1/sso/sessions/:id`         | DELETE | Revoke SSO session                                      |
| `/sso/login`                   | GET    | Get IdP authorization URL                               |
| `/sso/callback/oidc`           | POST   | OIDC callback (with verification)                       |
| `/sso/callback/saml`           | POST   | SAML callback                                           |
| `/sso/callback/ldap`           | POST   | LDAP direct-bind preview (no directory or group search) |
