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
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
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
// 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
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
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
// 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)
Always use ldaps:// (LDAP over TLS) in production. Unencrypted LDAP transmits passwords in plaintext.
Step 2: Create the LDAP connection
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:
// 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)
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.
Domain-based routing
When you assign domains to connections, Grantex automatically routes users to the correct IdP:
// 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:
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:
- User authenticates via SSO
- Grantex checks if a SCIM user exists with the IdP’s subject identifier
- If not found, creates a new SCIM user with the IdP’s email and display name
- If found, updates the existing user’s details
- Returns the
principalId in the callback result
SSO enforcement
Require all users in your organization to authenticate via SSO:
// Enable SSO enforcement
await grantex.sso.setEnforcement({ enforce: true });
// Disable SSO enforcement
await grantex.sso.setEnforcement({ enforce: false });
Enabling SSO enforcement marks all active connections as enforced. Ensure you have at least one active, tested SSO connection before enabling.
Session management
Track and manage active SSO sessions:
// 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
# 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 |