Configure SAML single sign-on with your identity provider, set up SCIM automated provisioning, and manage the user lifecycle.
This guide walks you through configuring enterprise single sign-on (SSO) for your organisation on Lifestream Vault.
By the end you will have:
This setup is entirely API-driven. You can automate every step with the SDK or CLI, making it easy to reproduce across staging and production environments.
SAML SSO and SCIM provisioning require a Business plan. Upgrade in Settings → Subscription. Standard email/password login and MFA (TOTP, passkeys) are available on all tiers.
Before starting, make sure you have the following in place:
Identity Provider (IdP)
You need admin access to one of the following (or any SAML 2.0-compliant IdP):
| Identity Provider | Notes |
|---|---|
| Okta | Use the "SAML 2.0" app template; SCIM is natively supported |
| Azure Active Directory | Create an Enterprise Application with SAML login; SCIM uses Azure AD provisioning |
| OneLogin | Use a custom SAML connector; SCIM requires a OneLogin provisioning profile |
| Google Workspace | Custom SAML app in Admin Console; SCIM support varies |
| Any other SAML 2.0 IdP | Must support HTTP-Redirect binding for login and HTTP-POST for assertion |
Lifestream Vault
POST /api/v1/admin/sso-configs must be an admin)npm install @lifestreamdynamics/vault-sdknpm install -g @lifestreamdynamics/vault-cliNetworking
https://vault.lifestreamdynamics.com/api/v1/auth/saml/:slug/callbackClock skew: SAML assertions include NotBefore and NotOnOrAfter timestamps. Lifestream Vault allows up to 5 minutes of clock skew. Ensure your IdP server time is synchronised with NTP. Clock skew is one of the most common causes of assertion validation failures.
The first step is to create an SSO configuration in Lifestream Vault. This stores the IdP connection details and returns the information you need to configure the other side (your IdP).
SSO config fields:
| Field | Description |
|---|---|
domain | Your organisation's email domain (e.g. acme.com). Users with this email domain are redirected to SSO at login. |
slug | A URL-safe identifier used in SAML endpoint paths (e.g. acme). Must be unique across all organisations. |
entityId | The IdP's Entity ID (also called Issuer) — a URI that identifies your IdP. Found in your IdP's SAML metadata. |
ssoUrl | The IdP's SSO endpoint URL where Lifestream Vault sends the authentication request (HTTP-Redirect binding). |
certificate | The IdP's X.509 public certificate in PEM format (without the -----BEGIN CERTIFICATE----- headers, or with — both are accepted). Used to verify assertion signatures. |
Obtain entityId, ssoUrl, and certificate from your IdP's SAML metadata XML or its admin console.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
import { readFileSync } from 'fs';
// Admin credentials required
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'admin@acme.com',
'admin-password',
);
// Read the IdP certificate from a local PEM file
const certificate = readFileSync('./idp-certificate.pem', 'utf8');
const ssoConfig = await client.saml.createConfig({
domain: 'acme.com',
slug: 'acme',
entityId: 'https://idp.acme.com/saml2/entity',
ssoUrl: 'https://idp.acme.com/saml2/sso',
certificate,
});
console.log('SSO config created:', ssoConfig.id);
console.log('ACS URL (give this to your IdP):',
`https://vault.lifestreamdynamics.com/api/v1/auth/saml/${ssoConfig.slug}/callback`);
console.log('SP Entity ID:',
`https://vault.lifestreamdynamics.com/api/v1/auth/saml/${ssoConfig.slug}/metadata`);In your IdP, you will need to set:
https://vault.lifestreamdynamics.com/api/v1/auth/saml/{slug}/callbackhttps://vault.lifestreamdynamics.com/api/v1/auth/saml/{slug}/metadataurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressemail and display name as displayNameReplace {slug} with the slug you chose (e.g. acme).
Most identity providers allow you to configure a SAML integration by uploading Service Provider (SP) metadata XML rather than entering each field manually. Lifestream Vault generates this metadata automatically for each SSO configuration.
The SP metadata is available at a public URL — no authentication required, because it only contains the SP's public certificate and endpoints, not any secrets.
Metadata URL format:
https://vault.lifestreamdynamics.com/api/v1/auth/saml/{slug}/metadata
For example, if your slug is acme:
https://vault.lifestreamdynamics.com/api/v1/auth/saml/acme/metadata
In your IdP admin console:
// Download the SP metadata XML as a string
const metadata = await client.saml.getMetadata('acme');
// Write it to a file to upload to your IdP
import { writeFileSync } from 'fs';
writeFileSync('./sp-metadata.xml', metadata);
console.log('Metadata saved to sp-metadata.xml');
// Or just log the metadata URL for your IdP to fetch directly
console.log('Metadata URL:',
'https://vault.lifestreamdynamics.com/api/v1/auth/saml/acme/metadata');The SP metadata XML is generated dynamically from the current SSO configuration. If you update the SSO config (e.g. rotate the SP certificate), re-download the metadata and re-upload it to your IdP. Some IdPs support a metadata URL that they poll periodically — using the URL instead of a static file means your IdP stays in sync automatically.
Before rolling SSO out to your whole organisation, test it with a single pilot user.
The SSO login flow works as follows:
1. User visits: https://vault.lifestreamdynamics.com/login
2. User enters their email (e.g. alice@acme.com)
→ Domain matches the "acme.com" SSO config
→ Lifestream Vault generates a SAML AuthnRequest
3. Browser is redirected to the IdP SSO URL (HTTP-Redirect binding)
4. IdP authenticates the user (password, MFA, etc. — IdP-managed)
5. IdP POSTs a signed SAML Assertion to the ACS URL:
https://vault.lifestreamdynamics.com/api/v1/auth/saml/acme/callback
6. Lifestream Vault validates the assertion:
- Checks signature against the stored certificate
- Verifies audience (SP Entity ID) and destination (ACS URL)
- Checks NotBefore / NotOnOrAfter window (±5 min clock skew)
- Verifies assertion ID has not been replayed (Redis-backed, 5-min TTL)
7. If valid, the user is signed in and receives an access token
→ New accounts are created automatically on first login
→ Existing accounts are matched by email
To test, open an incognito window (to avoid session interference) and go to the login page. Enter a pilot user's email address — the domain check should trigger the SSO redirect.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'admin@acme.com',
'admin-password',
);
// Retrieve the SSO config to confirm it was saved correctly
const configs = await client.saml.listConfigs();
const acmeConfig = configs.find((c) => c.slug === 'acme');
if (acmeConfig) {
console.log('Domain:', acmeConfig.domain); // acme.com
console.log('SSO URL:', acmeConfig.ssoUrl); // https://idp.acme.com/saml2/sso
console.log('Entity ID:', acmeConfig.entityId); // https://idp.acme.com/saml2/entity
console.log('Has certificate:', acmeConfig.certificate.length > 0);
} else {
console.error('SSO config not found!');
}Assertion replay prevention: Lifestream Vault stores every processed SAML assertion ID in Redis for 5 minutes (matching the NotOnOrAfter window). If the same assertion ID arrives twice, the second request is rejected with a 401. This prevents replay attacks where a captured assertion is reused to impersonate a user.
SCIM 2.0 (System for Cross-domain Identity Management) automates user provisioning. When you onboard a new employee in your IdP, SCIM creates their Lifestream Vault account automatically. When someone leaves and is deactivated in the IdP, SCIM suspends their access immediately — no manual cleanup required.
SCIM base URL:
https://vault.lifestreamdynamics.com/api/v1/scim/v2
Authentication: all SCIM requests use a Bearer token that you generate in Lifestream Vault and paste into your IdP's provisioning configuration. The token has no expiry by default but can be rotated at any time.
Step 1 — generate a SCIM token:
# The SCIM Bearer token is a static server-side environment variable.
# Set it in your API environment before starting the server:
# Generate a cryptographically secure random token (64+ characters recommended)
openssl rand -hex 64
# Add it to your .env or process manager config:
# SCIM_TOKEN=<the value from the command above>
# Then restart the API server
pm2 restart lsvault-api
# Your IdP should now send requests with:
# Authorization: Bearer <SCIM_TOKEN value>Step 2 — configure SCIM in your IdP:
| IdP | Where to configure |
|---|---|
| Okta | App → Provisioning → Integration → paste SCIM URL + token |
| Azure AD | Enterprise App → Provisioning → Tenant URL + Secret Token |
| OneLogin | App → Provisioning → enable, paste token |
Use these settings in your IdP:
https://vault.lifestreamdynamics.com/api/v1/scim/v2Rotate the SCIM token immediately if it is ever exposed. Generating a new token invalidates the previous one — update your IdP's provisioning configuration with the new token within a few minutes to avoid a provisioning outage.
Lifestream Vault implements the SCIM 2.0 core schema for Users and Groups. All endpoints are under the base URL /api/v1/scim/v2 and require a Bearer <scimToken> header.
User endpoints:
| Method | Path | Description |
|---|---|---|
GET | /Users | List users (supports filter, startIndex, count) |
GET | /Users/:id | Get a single user by SCIM ID |
POST | /Users | Create a new user |
PUT | /Users/:id | Replace user attributes (full update) |
PATCH | /Users/:id | Partial update (e.g. deactivate: active: false) |
DELETE | /Users/:id | Hard-delete a user (irreversible) |
Group endpoints:
| Method | Path | Description |
|---|---|---|
GET | /Groups | List groups |
GET | /Groups/:id | Get a single group |
POST | /Groups | Create a group |
PUT | /Groups/:id | Replace group |
PATCH | /Groups/:id | Update group membership |
DELETE | /Groups/:id | Delete a group |
Discovery endpoints (no auth required):
| Method | Path | Description |
|---|---|---|
GET | /ServiceProviderConfig | Advertises supported SCIM features |
GET | /Schemas | Lists all supported SCIM schemas |
GET | /ResourceTypes | Lists User and Group resource types |
Filter syntax example:
GET /api/v1/scim/v2/Users?filter=userName eq "alice@acme.com"
GET /api/v1/scim/v2/Users?filter=active eq false
GET /api/v1/scim/v2/Users?startIndex=1&count=25
SCIM user attributes map to Lifestream Vault fields as follows:
| SCIM attribute | Vault field |
|---|---|
userName | email |
name.formatted | displayName |
active | account active/suspended state |
emails[primary] | email |
curl -X POST https://vault.lifestreamdynamics.com/api/v1/scim/v2/Users \
-H "Authorization: Bearer $SCIM_TOKEN" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "alice@acme.com",
"name": {
"formatted": "Alice Nguyen"
},
"emails": [
{ "value": "alice@acme.com", "primary": true }
],
"active": true
}'Understanding how SCIM events translate to Vault account state is important for compliance and for avoiding accidental data loss.
Lifecycle event mapping:
| SCIM event | IdP action | Vault result |
|---|---|---|
POST /Users (active: true) | New hire added to app | Account created; user can log in via SSO |
PATCH /Users/:id (active: false) | Employee deactivated / offboarded | Account suspended; all sessions invalidated; login blocked |
PUT /Users/:id | Profile update (name, email) | displayName and email updated on existing account |
DELETE /Users/:id | User hard-deleted from IdP | Account permanently deleted including all vaults and documents |
Important distinction — deactivate vs. delete:
Recommendation: configure your IdP to send PATCH deactivations rather than DELETEs. Run a manual cleanup pass after a retention period if permanent deletion is required.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
// Create a client with scimToken to enable the SCIM resource
const client = new LifestreamVaultClient({
baseUrl: 'https://vault.lifestreamdynamics.com',
apiKey: process.env.VAULT_API_KEY!,
scimToken: process.env.SCIM_TOKEN!,
});
// Find a user by email via SCIM filter
const result = await client.scim?.listUsers({
filter: 'userName eq "bob@acme.com"',
});
if (!result || result.totalResults === 0) {
console.log('User not found');
process.exit(1);
}
const scimUser = result.Resources[0];
console.log('Found user:', scimUser.id, '— active:', scimUser.active);
// Suspend the user (soft deactivation — data preserved)
// updateUser performs a full PUT replacement; set active: false to suspend
await client.scim?.updateUser(scimUser.id, {
...scimUser,
active: false,
});
console.log('User suspended. All active sessions have been invalidated.');
// If needed, reactivate later
await client.scim?.updateUser(scimUser.id, {
...scimUser,
active: true,
});
console.log('User reactivated.');Provisioning on first SSO login: if a user logs in via SSO but was not pre-provisioned by SCIM (e.g. SCIM is not configured yet), Lifestream Vault automatically creates their account using the email and display name from the SAML assertion. Once SCIM is active, those auto-created accounts are matched and managed by SCIM going forward — you do not need to manually link them.
Most SSO problems fall into a small number of categories. Use this section to diagnose issues before contacting support.
Assertion validation errors:
| Error | Likely cause | Fix |
|---|---|---|
Audience mismatch | IdP Entity ID doesn't match the stored entityId | Update the SSO config with the correct entityId from your IdP |
Destination mismatch | IdP is posting to the wrong ACS URL | Set ACS URL in your IdP to exactly: /api/v1/auth/saml/{slug}/callback |
Signature verification failed | Stored certificate doesn't match the signing key | Re-download the IdP certificate and update the SSO config |
NotOnOrAfter expired | Assertion is older than 5 minutes | Check IdP server clock — enable NTP sync |
NotBefore in the future | Lifestream Vault's clock is behind the IdP | Check Vault server clock — usually a container time drift issue |
Assertion ID replayed | Same assertion received twice within 5 minutes | Possible network retry loop — check IdP retry settings |
NameID not found | Assertion is missing the email attribute | Ensure IdP sends NameID in emailAddress format |
SCIM provisioning errors:
| Error | Likely cause | Fix |
|---|---|---|
401 Unauthorized | Wrong or expired SCIM token | Generate a new token; update IdP provisioning config |
409 Conflict on user create | User already exists with that email | Existing accounts are matched by email — this is expected on first sync |
400 on PATCH | Invalid PatchOp schema | Ensure schemas field includes the PatchOp schema URN |
| Provisioning stops silently | Network timeout or IdP retry exhausted | Check IdP provisioning logs; ensure SCIM URL is reachable |
Debugging tools:
# Retrieve all SSO configs to verify stored values (and find a config ID)
lsvault saml list-configs
# Update a specific field without recreating the whole config
# (update-config takes the config ID as a positional argument)
lsvault saml update-config <CONFIG_ID> --certificate ./new-cert.pem
# Verify the SCIM endpoint is reachable (SCIM is enabled via the SCIM_TOKEN env var)
lsvault scim service-config
IdP certificate expiry: most IdP certificates are valid for 3–10 years, but some organisations rotate them annually or on a schedule. Set a calendar reminder to check your IdP's certificate expiry date. When the certificate rotates, you must update the Lifestream Vault SSO config with the new certificate before the old one expires, or all SSO logins will fail with a signature verification error. Update the certificate with lsvault saml update-config <CONFIG_ID> --certificate ./new-cert.pem or via the admin UI at Settings → SSO → Edit Config.
Your organisation now has a fully automated identity pipeline — users are created, updated, and deactivated in Lifestream Vault automatically as their IdP state changes, and they sign in with a single click through SSO. Here are some natural next steps: