Federated identity
Business and Enterprise installations can let an identity provider vouch for who a person is. Marolum still decides what that person may do: every federated identity maps to a Marolum account, role, and permission set, and no claim from the provider ever grants a permission directly.
Federation is additive. Local sign-in keeps working, a provider outage never removes it, and you keep a local administrator with a tested recovery procedure throughout.
How it works
browser ──▶ /api/v1/auth/<provider>/<issuerKey>/start ──▶ IdP ──▶ callback ──▶ Marolum
│
registered issuer + immutable subject ──▶ account
- A provider registration names an
issuerKey(yours, permanent), the exactissuer, the protocol, and a provisioning mode. - The identity Marolum trusts is the registered issuer plus the provider's immutable subject,
namely the OIDC
subclaim, the SAML persistentNameID, or the header a trusted proxy injects. Email, display name, groups, and roles are never used to select, link, or authorize an account, because all of them can change or be reassigned. administeredmode pre-links each subject to an existing account; an unknown subject fails without creating anything.on-first-logincreates a viewer on a verified first sign-in. Neither mode ever creates an administrator.
Before you begin
- A Business or Enterprise license on the installation (Administration → Licensing).
Business allows one enabled OIDC or SAML registration and refuses a second with
connection-limit; trusted proxy, SCIM, and additional connections require Enterprise. MAROLUM_PUBLIC_ORIGINset to the exact HTTPS origin browsers use, for examplehttps://marolum.example.com, on every replica.- An account with
accounts:manage, and a second local administrator you can still sign in with if the provider misbehaves. - Pick your issuer key now: 1–64 lowercase letters, digits, or dashes, starting with a letter or
digit. It appears in URLs and environment variable names and cannot be changed later. This
page uses
corp-idp.
Step 1: Register the provider
From Administration → Users & access → Federated identity, or:
POST /api/v1/identity/providers
{ "issuerKey": "corp-idp", "issuer": "<exact issuer>", "provider": "oidc", "provisioningMode": "administered" }
| Field | Value |
|---|---|
issuerKey |
corp-idp |
issuer |
OIDC: the issuer URL discovery advertises. SAML: the IdP entity ID. Trusted proxy: a realm name you choose |
provider |
oidc, saml, or trusted-proxy |
provisioningMode |
administered (recommended) or on-first-login |
The binding of key to issuer is permanent. To point corp-idp at a different issuer, register a
new key.
Before enabling the registration, verify its issuer key, exact issuer, protocol and provisioning mode against the identity provider. Keep a working local administrator account throughout the setup.
Step 2: Configure the identity provider
Give the provider these Marolum values. <origin> is MAROLUM_PUBLIC_ORIGIN.
OIDC
| Provider setting | Value |
|---|---|
| Application type | Web application, authorization-code flow |
| Redirect / callback URI | <origin>/api/v1/auth/oidc/corp-idp/callback: exact match, no wildcard |
| Scopes | openid (Marolum requests what it needs) |
| PKCE | Enabled (Marolum always uses it) |
Marolum discovers endpoints from the registered issuer and validates issuer, audience, signature, nonce, and PKCE on every round trip. Round trips are single-use and expire after ten minutes.
SAML
| Provider setting | Value |
|---|---|
| SP entity ID / audience | <origin>/api/v1/auth/saml/corp-idp |
| Assertion consumer service (ACS) | <origin>/api/v1/auth/saml/corp-idp/acs |
| Binding | HTTP-Redirect for SSO; HTTP-POST to the ACS |
| NameID format | urn:oasis:names:tc:SAML:2.0:nameid-format:persistent |
| NameID value | The user's immutable identifier, not username or email |
| Response signing | Signed responses. Encrypted assertions are refused |
SAML is SP-initiated only; IdP-initiated (unsolicited) responses are refused. Transient and email-address NameID formats are refused.
Trusted proxy
Your proxy authenticates the user, then forwards a request carrying an immutable subject in a
header and proves itself with a shared secret in x-marolum-trusted-proxy-secret.
- Strip any client-supplied copy of the subject and secret headers at the proxy before injecting the verified values.
- Restrict direct network access to Marolum so nothing but the proxy can reach it.
- Protect the proxy-to-Marolum hop (private network or TLS).
Marolum checks the secret and reads the subject; it does not install or configure the proxy.
Step 3: Set the protocol variables
<KEY> is the issuer key uppercased with dashes changed to underscores: corp-idp →
CORP_IDP. Set them on every replica through your platform's secret mechanism and restart.
| Provider | Variables |
|---|---|
| OIDC | MAROLUM_OIDC_CORP_IDP_CLIENT_ID, MAROLUM_OIDC_CORP_IDP_CLIENT_SECRET |
| SAML | MAROLUM_SAML_CORP_IDP_SSO_URL, MAROLUM_SAML_CORP_IDP_IDP_CERT (PEM), optional MAROLUM_SAML_CORP_IDP_NAMEID_FORMAT |
| Trusted proxy | MAROLUM_TRUSTED_PROXY_CORP_IDP_SUBJECT_HEADER, MAROLUM_TRUSTED_PROXY_CORP_IDP_SHARED_SECRET (≥ 32 characters), optional MAROLUM_TRUSTED_PROXY_CORP_IDP_DISPLAY_HEADER |
Missing or invalid variables refuse sign-in through that provider with a closed reason; they never affect local sign-in.
Step 4: Link people to accounts
In administered mode, link each subject before that person signs in:
POST /api/v1/identity/links
{ "issuerKey": "corp-idp", "subject": "<immutable subject>", "userId": "<Marolum user id>" }
The subject is the value the provider will send (an Okta user ID, an Entra object ID, a persistent NameID), not an email. Get it from the provider's user record.
In on-first-login mode nothing is linked in advance; a verified first sign-in creates a viewer
account. Promote it afterwards from Users & Access if the person needs more. Switch modes
with PUT /api/v1/identity/providers/corp-idp/mode.
Step 5: Test
Open <origin>/api/v1/auth/oidc/corp-idp/start (or saml, trusted-proxy) or use the
provider's action on the sign-in screen. Sign in at the provider. You land in Marolum as the
linked account.
Test in a fresh signed-out browser. Confirm the intended issuer action, complete the identity-provider round trip and verify the resulting Marolum account and permissions. A visible provider button alone does not establish successful sign-in.
Walkthroughs
Provider consoles change; the values are what matter.
Okta: OIDC
- Applications → Create App Integration → OIDC - OpenID Connect → Web Application.
- Sign-in redirect URI:
<origin>/api/v1/auth/oidc/corp-idp/callback. Leave sign-out redirects empty. - Note the Client ID and Client secret.
- Issuer: your org authorization server,
https://<org>.okta.com(or the custom authorization server's issuer,https://<org>.okta.com/oauth2/default, if you use one). Register that exact string. - Assign the users or groups who may sign in. Assignment gates access at Okta; it does not create Marolum accounts.
- The immutable subject is Okta's user ID (the
subclaim). Find it on the user's profile URL or through the Okta API when linking accounts.
Microsoft Entra ID: OIDC
- App registrations → New registration. Platform Web, redirect URI
<origin>/api/v1/auth/oidc/corp-idp/callback. - Certificates & secrets → New client secret. Note the value now; it is shown once.
- Note the Application (client) ID and the Directory (tenant) ID.
- Issuer:
https://login.microsoftonline.com/<tenant-id>/v2.0. Register that exact string. - Under Enterprise applications, set Assignment required and assign users, so only assigned people can start a sign-in.
- The
subclaim Entra issues is stable per user per application, which is what Marolum needs, but it is pairwise (not the user's object ID) and the portal does not display it. The practical path: register the provider inon-first-loginmode, have each person sign in once (Marolum creates a viewer bound to their subject), promote as needed, then switch the mode toadministeredso no further accounts are created.
Okta: SAML
- Applications → Create App Integration → SAML 2.0.
- Single sign-on URL:
<origin>/api/v1/auth/saml/corp-idp/acs. Audience URI (SP entity ID):<origin>/api/v1/auth/saml/corp-idp. - Name ID format: Persistent. Application username: a value that never changes for a user;
use the Okta user ID (
user.idin a custom expression), not email or username. - Leave assertion encryption off; Marolum refuses encrypted assertions. Sign the response.
- From the app's sign-on settings, copy the Identity Provider Single Sign-On URL (into
MAROLUM_SAML_CORP_IDP_SSO_URL), the Identity Provider Issuer (register it as theissuer), and download the X.509 certificate (intoMAROLUM_SAML_CORP_IDP_IDP_CERT).
Microsoft Entra ID: SAML
- Enterprise applications → New application → Create your own application → Integrate any other application (non-gallery), then Single sign-on → SAML.
- Identifier (Entity ID):
<origin>/api/v1/auth/saml/corp-idp. Reply URL (ACS):<origin>/api/v1/auth/saml/corp-idp/acs. - Unique User Identifier (Name ID): format Persistent, source attribute
user.objectid, neveruser.userprincipalnameoruser.mail. - Do not enable token encryption. Keep the default signed response.
- Copy the Login URL (into
MAROLUM_SAML_CORP_IDP_SSO_URL), the Microsoft Entra Identifier (register it as theissuer), and download Certificate (Base64) (intoMAROLUM_SAML_CORP_IDP_IDP_CERT).
SCIM provisioning
SCIM lets the provider create and deactivate Marolum accounts for the same immutable subjects. It uses its own registration-bound bearer credential, separate from browser sessions and integration tokens.
- Issue the token from Users & Access or
POST /api/v1/identity/scim-tokenswith{ "issuerKey": "corp-idp", "name": "okta-scim" }. Copy the secret once;GET /api/v1/identity/scim-tokensnever returns it again. - Give the provider the base URL
<origin>/api/v1/scim/v2/corp-idpand the token as the bearer secret. - Map
externalIdto the same immutable subject the sign-in path uses. IfexternalIdis omitted, Marolum treatsuserNameas that subject, not as an email lookup.
| Provider | Where |
|---|---|
| Okta | The app's Provisioning tab → Configure API integration: SCIM connector base URL, HTTP Header authentication with the token. Enable Create Users and Deactivate Users |
| Entra ID | The enterprise app's Provisioning page: Tenant URL and Secret Token, then Test Connection and assign users |
What SCIM does and does not do:
- Supported: user create, read, replace, patch, delete;
ServiceProviderConfig,ResourceTypes,Schemas; filtering by subject equality. - Creation assigns exactly the viewer role. Group and role attributes never grant Marolum permissions; promote people in Marolum.
- Deactivation revokes the identity's sessions. Delete means deactivate; historical rows remain.
- Not supported: groups, bulk operations, sorting, ETags, password management.
- A token sees only its own provider. Provisioning writes require the Enterprise entitlement; a lapse refuses writes without deleting identities.
Offboarding
- One person: disable the account, or unlink the identity, from Users & Access. Either revokes that identity's sessions in the same transaction. Deactivating through SCIM does the same.
- A whole provider: disabling the registration stops new sign-ins but does not revoke existing sessions; disable the accounts or wait out the twelve-hour session lifetime.
- Reconciliation for an
on-first-loginprovider accepts the provider's current subject set and disables absent identities. Empty sets are refused, and so is any action that would remove the last enabled administrator's last identity. A provider outage never counts as an empty directory. - Do not use license lapse as an offboarding control. A lapse retains registrations and identities and keeps configured sign-in paths working; only identity administration and SCIM writes stop.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Provider returns a redirect-URI error | The callback URI at the provider is not exactly <origin>/api/v1/auth/oidc/corp-idp/callback |
Fix it at the provider; check MAROLUM_PUBLIC_ORIGIN has no trailing slash |
| Sign-in refused after a successful IdP login | Issuer mismatch: the registered issuer differs from what discovery or the assertion reports |
Register the exact issuer string the provider publishes |
Refused with an unknown-subject reason (administered) |
No link exists for this subject | Link the subject, or use on-first-login for the first sign-in |
| SAML response refused | Encrypted assertion, unsolicited (IdP-initiated) response, or a non-persistent NameID | Turn off encryption, start from Marolum, set the NameID format to persistent |
| Round trip refused after a pause | Round trips expire after ten minutes and are single-use | Start again |
| Works on one replica, fails on another | Protocol variables missing on that replica | Apply identical settings to every replica and restart |
| Local sign-in also broken | Not federation: check TLS and MAROLUM_ALLOW_HTTP in Troubleshooting |
Federation has protocol fixtures, not a certification matrix for every provider. Validate your provider's immutable subject, protocol settings, and offboarding behavior on a test account before rolling out.