> ## 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, SAML 2.0, and LDAP single sign-on with multi-IdP connections, domain-based routing, JIT provisioning, and group-to-scope mapping.

## Overview

Grantex provides enterprise-grade SSO that supports **OIDC**, **SAML 2.0**, and **LDAP** protocols. You can configure multiple identity provider connections per organization, automatically route users to the correct IdP by email domain, and map IdP groups to Grantex scopes.

**Key capabilities:**

| Feature                 | Description                                                          |
| ----------------------- | -------------------------------------------------------------------- |
| Multi-IdP connections   | Configure multiple OIDC, SAML 2.0, and LDAP providers 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 / Active Directory | Direct bind authentication against LDAP directories                  |
| Domain routing          | Route users to the correct IdP based on email domain                 |
| JIT provisioning        | Auto-create principals on first SSO login                            |
| Group mapping           | Map IdP groups/roles to Grantex scopes                               |
| 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. Instead, the user submits their credentials directly to your application, and Grantex performs a **direct bind** against the LDAP directory server to verify them. This makes LDAP ideal for backend services, CLI tools, and environments where browser-based flows are not practical.

### 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 distinguished name used for the initial bind to search for users (e.g. `cn=readonly,dc=mycompany,dc=com`)
* **Bind password** — The password for the bind DN
* **Search base** — The base DN to search for user entries (e.g. `ou=people,dc=mycompany,dc=com`)
* **User attribute** — The attribute used to match the username (e.g. `uid` for OpenLDAP, `sAMAccountName` for Active Directory)
* **Group search base** — (Optional) The base DN for group lookups (e.g. `ou=groups,dc=mycompany,dc=com`)

<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',
  ldapUserAttribute: 'uid',
  ldapGroupSearchBase: 'ou=groups,dc=mycompany,dc=com',
  domains: ['mycompany.com'],
  jitProvisioning: true,
  groupMappings: {
    'cn=engineering,ou=groups,dc=mycompany,dc=com': ['read', 'write', 'deploy'],
    'cn=admins,ou=groups,dc=mycompany,dc=com': ['admin', 'read', 'write'],
  },
  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,
});

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

<Note>
  LDAP authentication performs a two-step process: first, the service account binds and searches for the user entry by the configured user attribute; then, a second bind is attempted with the user's own DN and password to verify their credentials.
</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-user-attribute uid \
  --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 | LDAP |
| --------------------------- | ---- | -------- | ---- |
| Okta                        | Yes  | Yes      | Yes  |
| Azure AD / Entra ID         | Yes  | Yes      | Yes  |
| Google Workspace            | Yes  | Yes      | -    |
| Auth0                       | Yes  | Yes      | -    |
| OneLogin                    | Yes  | Yes      | Yes  |
| PingFederate                | Yes  | Yes      | Yes  |
| JumpCloud                   | Yes  | Yes      | Yes  |
| OpenLDAP                    | -    | -        | Yes  |
| FreeIPA                     | -    | -        | Yes  |
| Apache Directory Server     | -    | -        | Yes  |
| Any OIDC-compliant IdP      | Yes  | -        | -    |
| Any SAML 2.0-compliant IdP  | -    | Yes      | -    |
| Any LDAPv3-compliant server | -    | -        | Yes  |

***

## 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 bind authentication          |
