SAML single sign-on
Experimental, off by default. SAML sign-in and SCIM provisioning sit behind theConnect one or more SAML 2.0 identity providers (IdPs). Each enabled connection adds a button to the sign-in page. Directory groups can decide each person’s role, and you can require single sign-on for everyone except root users.saml-scimbeta feature: turn it on under Settings, Beta features or withAPP_EXPERIMENTAL=saml-scim. While off, the routes answer404, the settings page and the sign-in button are hidden, and a “require single sign-on” setting is not applied. The service provider is the sign-in library’s native SAML implementation; Levelrail adds a stricter response check, account policy and role mapping around it. It is tested against a signing test identity provider, not yet against a live Okta, Entra ID or Keycloak tenant (see What has not been verified).
Before you start
- An https dashboard URL. Set it under Settings, General. Identity providers post sign-in responses to it, and the SP entity ID and assertion consumer service (ACS) URL are built from it, never from the request’s Host header. When the dashboard URL changes, the connection’s URLs follow at its next use.
- The control plane master key. The service provider signing key is generated at the first start with a master key and stored encrypted with it. Without one, SAML stays off and the settings page says so.
- Root. Every SSO setting needs a root session or token.
Set up a connection
- Open Settings, Single sign-on and choose Add SAML connection. Give it the name people will see on the button and save it.
-
Open Configure. Copy the three service provider values into a new SAML app at your IdP:
- Back in the sheet, paste the IdP metadata URL or XML and choose Import metadata. That fills the IdP entity ID, sign-in URL and signing certificate. When the metadata lists several signing certificates, the newest one valid today is kept.
- Pick who may sign in (see Accounts), turn on Show on the sign-in page and save.
What is checked on every sign-in
Sign-in is SP-initiated only: it always starts from the button on the sign-in page. Every response passes two checks, and the browser lands back on the sign-in page with a short error (and the reason goes to the control plane log and the audit log) if either fails. Levelrail’s check, before the library sees the response:InResponseTonames the AuthnRequest this browser started, and the assertion carries a bearer confirmation for that same request. Each request ID is single use, expires after ten minutes, and is bound to the browser by a secure cookie. This is what refuses IdP-initiated, forwarded and replayed responses: the library on its own accepts IdP-initiated responses and does not checkInResponseTo.- The assertion names this connection’s entity ID as its audience. An assertion without an audience restriction is refused.
- The response carries a valid XML signature from the stored IdP certificate, the issuer is the configured IdP entity ID,
Destinationand the bearerRecipientare this connection’s ACS URL,NotBeforeandNotOnOrAfterhold (three minutes of clock skew), and the response and assertion are no older than 90 seconds.
- The same signature, issuer, audience and time checks, plus: the assertion itself must be signed. A response that is signed only on the outer element is refused.
Accounts and roles
A person is matched in this order:- A subject (NameID) already linked to an account through this connection.
- An existing account with the same email. The subject is linked to it.
- A new account, only when Create accounts on first sign-in (
--jit) is on. Without it, people need an account first (for example an invite).
How this maps onto the library
The sign-in library groups SAML connections, SCIM tokens and directory users under organizations. Levelrail runs exactly one library organization, the instance, created at first start. It is the SAML and SCIM tenant and nothing else: Levelrail teams and their roles stay in Levelrail and are not library organizations.Group to role mappings
Map IdP groups to stored roles under Group to role mappings, or:--sync-roles), the role is re-applied at each sign-in, so a group change at the IdP takes effect at the next one. With sync on and no default role, a person in no mapped group is refused. The last root user is never demoted by a mapping, so directory drift cannot lock the instance out. The same mappings drive SCIM provisioning groups.
The groups attribute defaults to http://schemas.xmlsoap.org/claims/Group, the email attribute to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress and the name attribute to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name. Set the attribute names on the connection when your IdP sends something else (for example email and groups).
Require single sign-on
Turn on Require SSO for non-root users (orlevelrail-cli sso settings set --enforce=true). Password, passkey and email code sign-in are then refused for every user without the root ability. OAuth and OIDC sign-in stay available, since those are single sign-on too. It can only be turned on while at least one connection is enabled.
Break-glass access
Root users always keep password sign-in, so a broken IdP never locks the instance. Keep at least one root account with a strong password in your password manager. If that password is lost, reset it on the host:IdP setup guides
These follow each IdP’s documented SAML setup. They have not been run end to end against these IdPs yet.Okta
- Applications, Create App Integration, SAML 2.0.
- Single sign-on URL: the ACS URL. Check Use this for Recipient URL and Destination URL.
- Audience URI (SP Entity ID): the entity ID.
- Name ID format: EmailAddress. Application username: Email.
- Attribute statements:
email=user.email,displayName=user.displayName. Set the connection’s email attribute toemailand name attribute todisplayName. - Group attribute statements: name
groups, filterMatches regex.*(or a narrower filter). Set the connection’s groups attribute togroups. - Finish, then on the Sign On tab copy the Metadata URL and import it.
Microsoft Entra ID
- Enterprise applications, New application, Create your own application, then Set up single sign on, SAML.
- Basic SAML Configuration: Identifier (Entity ID) = the entity ID, Reply URL (ACS) = the ACS URL. Leave Sign on URL empty.
- Attributes & Claims: keep the default email claim, which matches the default email attribute. Add a group claim and set the connection’s groups attribute to
http://schemas.microsoft.com/ws/2008/06/identity/claims/groups. Choose Cloud-only group display names to receive names, or put the group object IDs in your mappings. - Copy the App Federation Metadata Url from the SAML Certificates section and import it.
- Assign users or groups to the application, or Entra refuses them.
Keycloak
- In the realm, Clients, Create client, type SAML, Client ID = the entity ID.
- Valid redirect URIs: the ACS URL. Master SAML Processing URL: the ACS URL.
- Keys tab: turn Client signature required off unless you upload the service provider certificate.
- Signature and Encryption: keep Sign assertions on. Signing only the document is refused.
- Client scopes, dedicated scope, Add mapper: a User Property mapper for
email(SAML attribute nameemail) and a Group list mapper with attribute namegroupsand Full group path off. Set the connection’s attribute names to match. - Import
https://<keycloak>/realms/<realm>/protocol/saml/descriptor.
What has not been verified
- Sign-in against a live Okta, Entra ID or Keycloak tenant. The tests sign responses with a local test IdP built on the same SAML library.
- Encrypted assertions.
- An IdP certificate rotation: a connection holds one signing certificate, so the new certificate has to be imported at the moment the IdP switches.
Troubleshooting
The control plane log and the audit log (
sso.saml.rejected) record the exact reason. Assertion contents are never logged.
API
The sign-in library’s own SAML and organization routes are not exposed; only the routes above reach it.