oxagenDocs
Governance

Single sign-on

Register an OIDC or SAML identity provider, verify your domain, map IdP groups to organization roles, require SSO, and provision people with SCIM from Okta or Microsoft Entra ID.

This guide sets up single sign-on (SSO) for an Oxagen organization. People from your email domain sign in through your identity provider (IdP), and the groups the IdP sends decide their organization role. Oxagen supports OIDC and SAML 2.0 for sign-in, and SCIM 2.0 for creating and removing people.

Before you start

Your organization needs the Enterprise plan. On any other plan, Oxagen refuses to add, edit, or verify a provider, to map groups, and to turn on Require SSO.

You need the Owner or Admin role in the Oxagen organization.

You need admin access to your IdP, such as Okta, Microsoft Entra ID, or Google Workspace.

You need to be able to publish a DNS TXT record for the email domain.

Pick a provider id before you begin. It is a slug of 2 to 63 lowercase letters, digits, or hyphens, such as acme-okta. It appears in the redirect URLs, so you cannot change it later without reconfiguring the IdP.

One email domain belongs to one organization. A second organization cannot register a provider for a domain that already has one.

The redirect URLs below use https://app.oxagen.sh. The page in the app shows the exact URLs for your provider, each with a copy button.

Register an OIDC provider

  1. In your IdP, create an OIDC web application.
  2. Set its sign-in redirect URI to https://app.oxagen.sh/api/auth/sso/callback/<providerId>.
  3. Configure the IdP to send a groups claim in the ID token or the userinfo response.
  4. Copy the issuer URL, the client ID, and the client secret from the IdP.
  5. In Oxagen, open Organization › Single sign-on (/{org}/sso).
  6. Choose OIDC as the protocol.
  7. Enter the provider id, a display name, and your email domain.
  8. Enter the issuer URL, the client ID, and the client secret.
  9. Leave the groups claim as groups, or enter the claim name your IdP uses.
  10. Save the provider.

Oxagen reads the issuer's discovery document (/.well-known/openid-configuration) when you save. The issuer URL must use https and name a public host. Oxagen refuses a private, loopback, or link-local address.

Oxagen requests the openid, email, and profile scopes. Add a scope, such as groups, when your IdP only sends groups for that scope.

After you save, the page shows whether a client secret is set. It never shows the secret. To keep the stored secret during an edit, leave the field empty.

Register a SAML provider

  1. In your IdP, create a SAML 2.0 application.
  2. Set the assertion consumer service (ACS) URL to https://app.oxagen.sh/api/auth/sso/saml2/sp/acs/<providerId>.
  3. Set the audience, or SP entity ID, to https://app.oxagen.sh/api/auth/sso/saml2/sp/metadata?providerId=<providerId>.
  4. Add an attribute statement named groups that carries the person's groups.
  5. Copy the IdP entity ID, the IdP single sign-on URL, and the IdP signing certificate.
  6. In Oxagen, open Organization › Single sign-on (/{org}/sso).
  7. Choose SAML as the protocol.
  8. Enter the provider id, a display name, and your email domain.
  9. Enter the IdP entity ID, the single sign-on URL, and the signing certificate in PEM form.
  10. Set the groups claim to the attribute name from step 4.
  11. Save the provider.

The SP metadata URL in step 3 also serves Oxagen's SP metadata. Import it when your IdP reads metadata from a URL.

To have Oxagen lock each sign-in request it sends (an AuthnRequest), paste an SP private key in PEM form. A locked request lets your IdP check that it came from Oxagen unchanged. Oxagen stores the key encrypted, and the page shows only that a key is set.

Oxagen accepts a response when the IdP locks the Assertion or the whole Response, because most IdPs lock only the Assertion. To require a lock on the whole Response, set wantResponseSigned to true when you create or update the provider through the API or MCP. With it on, Oxagen refuses a response whose whole Response carries no valid lock.

Oxagen refuses an assertion that has no NotBefore and NotOnOrAfter conditions, and an assertion locked with a deprecated algorithm such as SHA-1.

The MCP tools create_sso_provider and update_sso_provider take no secrets, because a tool argument lands in the agent's transcript. Enter a client secret or SP private key on the Single sign-on page or send it to the API. Over MCP you can register a SAML provider without an SP private key and change any setting except a secret.

Verify your domain

Nobody signs in through a provider until its domain is verified.

  1. Copy the TXT record name and value from the provider on the Single sign-on page.
  2. Publish a TXT record named _oxagen-sso.<domain> with the value oxagen-sso-verification=<token>.
  3. Wait for the record to reach public DNS.
  4. Press Verify domain.

One provider verifies a domain. Oxagen also refuses to verify a subdomain, such as eng.acme.com, while another organization has verified a domain above it, such as acme.com, because sign-in sends that subdomain to the parent's provider.

Verification also lets a person who already has an Oxagen password account for that email link it to SSO on their first SSO sign-in.

Map groups to roles

  1. Open Organization › Roles.
  2. Find the IdP group mappings section.
  3. Choose the provider.
  4. Add a row for each IdP group that should reach the organization.
  5. Enter the group name exactly as the IdP sends it.
  6. Choose the role: admin, compliance, billing, or member.
  7. Save the mappings.

Group names are case-sensitive. Engineering and engineering are two groups.

Owner cannot be mapped. Ownership passes only by a transfer inside Oxagen.

Mapping denies by default. A group with no row grants nothing.

On every SSO sign-in, Oxagen replaces the person's organization role with the highest role their groups map to. The order is admin, then compliance, then billing, then member.

A person whose groups match no row loses their organization role and their membership.

SSO never changes an Owner.

Send groups from your IdP

For OIDC, Oxagen reads the groups claim from the userinfo response and from the ID token, and uses the groups from both. It checks the lock on the ID token, its issuer, and its audience before it reads a group from it. A sign-in whose ID token carries groups but fails that check is refused.

  • Okta: add a groups claim to the app's ID token and set a groups filter, such as "Matches regex .*" or a name prefix.
  • Microsoft Entra ID: add a group claim under Token configuration. Entra sends group object IDs by default. Map the IDs, or change the group claim to emit names where your tenant supports it.
  • Google Workspace: Google does not send groups over OIDC, so an OIDC provider for Google grants no role to anyone. Register Google as a SAML provider and map group membership to the groups attribute.

Require SSO

  1. Verify at least one provider's domain.
  2. Open Organization › Single sign-on.
  3. Turn on Require SSO.

With SSO required, Oxagen refuses password, Google, and GitHub sign-in for an email on your providers' domains, unless the person is an Owner.

Members other than Owners reach the organization only with a session one of its SSO providers created. Oxagen sends anyone else to /login?sso=required.

Owners keep password access, so an IdP outage cannot lock the organization out.

The API applies the same rule to a browser session. A call it receives from such a session, through the app or directly, gets 403 with error.reason sso_required.

Require SSO also covers API keys and the MCP server. A key authenticates only while the person who created it is a member of your organization and has signed in at least once through one of its verified providers. Owners are exempt, the same break-glass rule as the app. The API answers any other key with 403 and "This organization requires single sign-on. The person who created this key must sign in through SSO.", and the MCP server refuses the call, until that person signs in through SSO. This includes machine keys: a recorder host key or gateway key that an Admin enrolled stops working until that Admin signs in through SSO once.

An SSO sign-in that no mapped group admits removes the person from the organization the way a SCIM group change does: their roles at every scope, their workspace memberships, and every key they created here. It leaves their sessions alone, because they may belong to other organizations.

Provision people with SCIM

Your IdP can create people in Oxagen before their first sign-in and remove them when they leave. Oxagen serves SCIM 2.0 at one endpoint per organization, authenticated by a bearer token only that organization's Owners and Admins can mint. SCIM uses the same IdP group mappings as SSO sign-in.

What SCIM changes in Oxagen

  • A pushed user exists before their first sign-in. The userName must be an email on one of your verified domains, or a subdomain of one. When an Oxagen account already uses that email, SCIM links it instead of creating a second one. Creating a user grants no role.
  • Groups decide roles. When a group's members change, Oxagen recomputes the organization role of each person the change touches, through the IdP group mappings on Organization › Roles. A mapping matches a SCIM group by its display name or by its external ID. SCIM reads the mappings of every provider in your organization, because a SCIM token belongs to the organization rather than to one provider. The highest mapped role wins. A person left in no mapped group loses their organization role and their membership, as they would at an SSO sign-in.
  • Deactivating or deleting a user deprovisions them, in one transaction. Oxagen ends their sessions (see below), revokes every API key they created in the organization (CLI session keys and machine keys included), revokes each recorder host they enrolled, expires any unused enrollment token issued to them, and removes every role assignment and their membership. Their API keys answer 401, and a person on your domain is signed out of their browser on its next request.
  • A session belongs to the person, not to one organization. When the person's email is on one of your verified domains, deprovisioning signs them out of every organization they belong to: your IdP owns that identity, so its removal ends it everywhere. A member whose email is on another domain, such as a contractor invited on a personal address, loses their access to your organization and their keys here, and keeps their sessions in other organizations.
  • SCIM changes the Oxagen account only for identities you own. An Oxagen account is shared by every organization its person belongs to. SCIM changes a person's email only when their current email is on one of your verified domains, and never an Owner's email. It answers 403 otherwise. A name change for anyone else is kept in your organization only.
  • A removal inside Oxagen lasts until your IdP next speaks. If an Owner or Admin removes someone on the Organization page while your IdP still assigns them to a mapped group, the next group change your IdP pushes admits them again, as their next SSO sign-in would. Unassign them in your IdP too.
  • An SSO sign-in does not re-admit a deprovisioned person. Reactivate them in your IdP. The reactivation reaches Oxagen over SCIM and their groups decide their role again.
  • Owners are never changed. SCIM refuses to deactivate or delete an Owner and answers 403 with the reason. A group change never alters an Owner's role. Ownership passes only by a transfer inside Oxagen.
  • Keys go with the person who created them. Before you deprovision someone who registered agents or enrolled hosts other people rely on, re-register those agents or re-enroll those hosts under someone who is staying.

Removing someone from a group is not a deprovisioning. It recomputes their role, and it removes their membership only when no mapped group is left.

Generate the SCIM token

  1. Open Organization › Single sign-on (/{org}/sso).
  2. In SCIM provisioning, copy the SCIM base URL. It is https://app.oxagen.sh/api/scim/v2.
  3. Choose Generate token.
  4. Copy the token. Oxagen stores only its hash and cannot show it again.

To replace the token, choose Rotate token. The old token stops working at once, so enter the new one in your IdP straight away. To stop provisioning, choose Revoke token. People your IdP already created keep their access.

Connect Okta

  1. In the Okta Admin Console, open the Oxagen app you created for SSO.
  2. On the General tab, edit App Settings and turn on SCIM provisioning. Save.
  3. On the Provisioning tab, choose Integration and edit it.
  4. Set SCIM connector base URL to the SCIM base URL.
  5. Set Unique identifier field for users to userName.
  6. Under Supported provisioning actions, select Push New Users, Push Profile Updates, and Push Groups.
  7. Set Authentication Mode to HTTP Header and paste the token as the bearer token.
  8. Choose Test Connector Configuration, then save.
  9. Under Provisioning › To App, enable Create Users, Update User Attributes, and Deactivate Users.
  10. Assign the people and groups who should reach Oxagen.
  11. On Push Groups, push each group you mapped on the Roles page.

Okta deactivates an unassigned or deactivated person with active: false, which Oxagen treats as a deprovisioning.

Connect Microsoft Entra ID

  1. In the Microsoft Entra admin center, open Enterprise applications and the Oxagen app you created for SSO.
  2. Open Provisioning and set Provisioning Mode to Automatic.
  3. Set Tenant URL to the SCIM base URL.
  4. Set Secret Token to the token.
  5. Choose Test Connection, then save.
  6. Under Mappings, keep users and groups enabled. Map userName to an attribute that holds the person's email on your verified domain, such as userPrincipalName or mail.
  7. Under Users and groups, assign the people and groups who should reach Oxagen.
  8. Set Provisioning Status to On and save.

Entra ID sends a group's object ID as its external ID. If you mapped group object IDs for SSO sign-in, the same rows work for SCIM. Entra ID provisions on a cycle, so a change can take up to 40 minutes to arrive unless you provision on demand.

What the endpoint serves

ResourceMethods
/ServiceProviderConfig, /ResourceTypes, /SchemasGET
/UsersGET with a userName, externalId, or emails.value eq filter, POST
/Users/{id}GET, PUT, PATCH, DELETE
/GroupsGET with a displayName or externalId eq filter, POST
/Groups/{id}GET, PUT, PATCH, DELETE

Oxagen keeps the core User attributes userName, name, displayName, emails, externalId, and active, and the core Group attributes displayName, externalId, and members. It ignores the rest, so your IdP can send its full profile. It accepts PATCH operations in the shapes Okta and Entra ID send, including capitalized operation names and the booleans "True" and "False" as strings. A group member your IdP has not provisioned into this organization is not stored.

Delete a provider

Deleting a provider on the Single sign-on page also signs out everyone it signed in.

  • Oxagen ends every session the provider created. Those people sign in again another way.
  • Oxagen removes the links between the provider and Oxagen accounts. A person keeps their Oxagen account and any password they set.
  • The provider's group mappings go with it.
  • If Require SSO is on and no verified provider remains, Oxagen turns it off.

Each ended session writes a security.session_revoked row.

Leaving the Enterprise plan

When your organization moves off the Enterprise plan:

  • Its providers stop signing people in. Oxagen keeps their settings.
  • Require SSO stops applying, so password, Google, and GitHub sign-in work again for your domains.
  • Members sign in with a password, or with Google or GitHub. A member who never set a password sets one with Forgot password on the login page.
  • An Owner or Admin can still delete providers and turn off Require SSO.
  • The SCIM endpoint refuses every request. An Owner or Admin can still revoke the SCIM token.

Sign in

  1. Open the Oxagen login page.
  2. Choose Sign in with SSO.
  3. Enter your work email.
  4. Choose Continue with SSO.
  5. Sign in at your IdP.

Oxagen finds the provider from the email domain and sends you back signed in.

Audit events

Each change and each SSO sign-in writes a row to security.security_events. Read them on Organization › Audit.

EventWritten when
sso.provider_createdA provider is registered
sso.domain_verifiedA domain passes verification
sso.provider_updatedA provider's settings change
sso.provider_deletedA provider is removed
security.session_revokedDeleting a provider ends a session it created
sso.policy_updatedRequire SSO is turned on or off
sso.group_roles_setThe group mappings are saved
sso.sign_inA person signs in through SSO
scim.token_createdAn Owner or Admin generates the SCIM token
scim.token_rotatedThe SCIM token is replaced
scim.token_revokedThe SCIM token is revoked
scim.user_provisionedYour IdP creates or links a person
scim.user_updatedYour IdP changes a person's email, name, or external ID, or reactivates them
scim.user_deprovisionedYour IdP deactivates or deletes a person. Its detail counts the sessions, keys, and hosts it ended
scim.group_changedYour IdP creates, changes, or deletes a group. Its detail lists members added and removed and the roles recomputed
scim.request_deniedThe endpoint refuses a request: a revoked token, a plan without SSO, an Owner, a domain you have not verified, or an identity on a domain you do not own
tacho.host_revokedA removal revokes a recorder host the person enrolled

An sso.sign_in row carries outcome success when the sign-in granted a role, deny when no group mapped, and error when Oxagen could not apply the role. Its detail holds providerId, groups, grantedRole, previousRole, and reason. The row also records the sign-in request's IP address and user agent.

Troubleshooting

No provider for the domain

The login page says no single sign-on is set up for the email domain.

  1. Check that the email's domain matches the provider's domain exactly.
  2. Register a provider for that domain if none exists.

Domain not verified

The login page says single sign-on is waiting on domain verification.

  1. Query the TXT record with dig TXT _oxagen-sso.<domain> +short.
  2. Compare the value with the one on the Single sign-on page.
  3. Press Verify domain again.

Groups missing from the token

The sso.sign_in row shows an empty groups list.

  1. Check that the IdP sends the claim or attribute named in the provider's groups claim.
  2. For OIDC, check whether the IdP needs an extra scope to send groups.

Signed in but no role

The person signs in and cannot open the organization. The sso.sign_in row shows outcome deny and reason no_mapped_group.

  1. Read the groups the row recorded.
  2. Add a mapping for one of those groups, spelled exactly as recorded.
  3. Ask the person to sign in again.

Entra ID sends group object IDs unless you configure it to send names. When the recorded groups are GUIDs, map the GUIDs or change the claim.

Locked out

The IdP is down or misconfigured, and members cannot sign in.

  1. Sign in as an Owner with email and password.
  2. Turn off Require SSO, or fix the provider settings.

An Owner who also forgot their password resets it from the login page.

SCIM answers 401

The token was rotated or revoked, or your IdP holds an old copy.

  1. Choose Rotate token on the Single sign-on page.
  2. Enter the new token in your IdP.

SCIM refuses a user's domain

The IdP sent a userName outside your verified domains. The scim.request_denied row carries reason domain_not_verified.

  1. Register and verify a provider for that domain, or change the attribute your IdP maps to userName.

A group does not change anyone's role

  1. Open Organization › Roles and check the IdP group mappings.
  2. Map the group by the exact name your IdP pushes, or by its external ID.
  3. Read the scim.group_changed row: rolesRecomputed shows the role each person holds after the change.

Previews

On a preview deployment, the IdP sends people back to the host in BETTER_AUTH_URL. The OAuth proxy that relays Google and GitHub sign-in on previews does not relay SSO. Test SSO on a deployment whose BETTER_AUTH_URL is the host you registered in the IdP.

On this page