Security
Single sign-on
Your team signs in through your own identity provider, so joiners and leavers are handled where you already handle them. Any provider that speaks OpenID Connect works, an admin sets it up in a few minutes, and there is a test button that proves the round trip before anyone else is affected.
What you need from your provider
Create an application in your identity provider (Okta, Entra ID, Keycloak, Google Workspace, anything OIDC) and collect four things:
| Setting | What it is |
|---|---|
| Issuer URL | The base your provider publishes its discovery document under, for example https://login.example.com. We read /.well-known/openid-configuration there to find your sign-in endpoints, so a typo is caught on the screen where you typed it rather than at your first employee's first sign-in. |
| Client ID | From the application you just created. |
| Client secret | Stored encrypted and never returned by the API. Leave the field empty on a later edit to keep the one you already saved. |
| Email domains | The domains that route to you, for example acme.com, acme.co.uk. Anyone signing in with an address at these is sent to your provider. A domain belongs to exactly one workspace across Engram, so a domain already claimed elsewhere is refused with a 409. |
In your provider's application you need one redirect URL from us, which is the same string for every customer: https://api.engramdynamics.org/auth/sso/callback. The security page shows the exact value for the deployment you are on, and so does GET /enterprise/sso in the redirect_uri field, on any plan and before anything is configured. We ask for the scopes openid email profile and nothing else, so no directory read permission is involved.
Configure it
In the app: Settings, Security, Single sign-on. Over the API, one call writes the whole configuration:
curl -s -X PUT https://api.engramdynamics.org/enterprise/sso \
-H "Authorization: Bearer <your key>" \
-H "Content-Type: application/json" \
-d '{
"issuer": "https://login.example.com",
"client_id": "engram-web",
"client_secret": "<client secret>",
"email_domains": ["acme.com", "acme.co.uk"],
"enforced": false
}'
{
"enabled": true,
"issuer": "https://login.example.com",
"client_id": "engram-web",
"client_secret_set": true,
"email_domains": ["acme.com", "acme.co.uk"],
"enforced": false,
"redirect_uri": "https://api.engramdynamics.org/auth/sso/callback",
"start_url": "https://app.engramdynamics.org/login?sso=1"
}
The write is validated against the real provider before anything is stored: we fetch your discovery document and check it names the same issuer and carries the endpoints the flow needs. A bad issuer fails here, with a plain-words message, instead of silently later.
Test it before you require it
The test button hands you an authorization URL built exactly the way a real sign-in builds one. Open it in a new tab: a wrong client id or an unregistered redirect URL shows up as your provider's own error, on your provider's own page, which is the clearest place for it to appear.
curl -s -X POST https://api.engramdynamics.org/enterprise/sso/test \
-H "Authorization: Bearer <your key>"
{
"authorization_url": "https://login.example.com/authorize?response_type=code&...",
"redirect_uri": "https://api.engramdynamics.org/auth/sso/callback",
"expires_in": 600
}
How a sign-in works
A person types their work address on the sign-in page. We map the domain to your workspace and send them to you:
curl -s -X POST https://api.engramdynamics.org/auth/sso/start \
-H "Content-Type: application/json" \
-d '{"email": "dana@acme.com"}'
They authenticate with you, your provider calls the one redirect URL back, and we mint the session. Points worth knowing:
- New people are provisioned on the spot, as members, with the address already verified, because your directory vouching for it is stronger proof than our verification email.
- The seat cap applies to an SSO join exactly as it applies to an invite, so auto-provisioning cannot quietly grow the workspace past the plan it bought.
- An existing admin stays an admin. SSO changes how someone signs in, not what they can do.
- An address that belongs to a different workspace is refused rather than moved.
- The sign-in attempt is carried in a signed, single-use state token that expires in ten minutes and can never be used as a session token.
- A sign-in through your provider is remembered the way a remembered password sign-in is, because your provider has already decided to trust the device. An operator can set
FEDERATED_PERSISTENT_SESSION=falseso Google and company sign-ins expire on the same schedule as a plain password login. See Staying signed in.
Requiring it
Turn on Require it, or send "enforced": true, and passwords stop working for the whole workspace: a password sign-in is refused and the person is sent to your provider instead. Test the round trip first.
Enforcement is the setting that can lock a workspace out, so it is also the setting we never let a plan trap you on. Turning single sign-on off is not plan-gated on any tier: a workspace that moves down to a plan without SSO can always switch it off and get its passwords back.
curl -s -X DELETE https://api.engramdynamics.org/enterprise/sso \
-H "Authorization: Bearer <your key>"
That forgets the whole configuration, the stored secret included.
When it does not work
| What you see | What it means |
|---|---|
409 on save, naming a domain | Another workspace already claims that domain. Get in touch if that is not right. |
400 on save, about the discovery document | The issuer URL is wrong or unreachable. Many providers publish per-realm, so the issuer is https://host/realms/acme, not the host root. |
sso_not_configured | The address does not map to a workspace with SSO turned on. |
sso_expired | The sign-in attempt took longer than ten minutes. Start again from the sign-in page. |
sso_wrong_workspace | Your provider asserted an address that already belongs to another workspace. |
seat_limit | The workspace is at its seat cap, so the new person was not provisioned. See the plan table. |
403 plan_feature on write | Reading the settings is open on every plan; writing them needs the plan that carries single sign-on. |
Next
IP allowlist restricts where requests can come from, and pairs naturally with single sign-on. API keys and scopes covers the machine credentials, which SSO does not replace.