SCIM provisioning from Entra ID and Okta, and single sign-on for the organisation
-
An organisation that keeps its people in an identity provider wants two things of its mail server: mailboxes that follow the directory, and a sign-in that goes through it. Since 6.3.4 hMailServer has both. SCIM 2.0 at
/scim/v2lets Microsoft Entra ID or Okta make, rename, suspend and remove accounts, with their aliases and distribution lists. Single sign-on, by OpenID Connect or SAML 2.0, opens the webmail at/portaland the Control Deck at/. The one thing to know first: they answer different questions and take different credentials. SCIM answers to an API key whose role holdsprovisionand to nothing else. Single sign-on opens the two pages only: IMAP, POP3 and SMTP still take the account's password and its application passwords.Passkeys and account recovery are covered in the topic on signing in.
How the pieces fit
Piece Direction What it does LDAP directory authentication This server asks the directory Checks a password when somebody signs in SCIM provisioning The identity provider calls this server Says what changed, as it changes Single sign-on The browser goes to the identity provider and back Signs a person in to the webmail or the Control Deck oidc_auto_provision,saml_auto_provisionIn passing, at sign-in Makes a mailbox for whoever arrives with a verified address, and never touches it again SCIM is the deliberate version of auto-provisioning. The two work together: SCIM keeps the mailboxes, and single sign-on is how the people it made sign in to them.
Where the settings are. The single sign-on settings are the REST API's and the Control Deck's. There is no COM property and no Control Panel page for them, on Windows or on Linux.
SCIM: the key and the permission
A SCIM request is a request of the REST listener.
RestApiPorthas to be on and the listener reachable from the identity provider's service over TLS, with a certificate it trusts (RestApiCertificateFile, or the ACME certificate). A body is at most 64 KB, and the key's requests count against the listener's budget of 200 in ten seconds per credential, beyond which the answer is429withRetry-After.The key's role must hold
provision. The built-inserveradminrole holds it, so every key issued with"scope":"full"provisions; no other built-in role does, so a read-only key is answered403. The way to issue a key that provisions and does nothing else is a custom role:curl -u Administrator:your-admin-password -H "Content-Type: application/json" \ -d '{"name":"entra-provisioning","description":"Microsoft Entra ID","permissions":"provision"}' \ https://mail.example.com:8443/api/v1/roles curl -u Administrator:your-admin-password -H "Content-Type: application/json" \ -d '{"label":"Entra provisioning","role":"entra-provisioning"}' \ https://mail.example.com:8443/api/v1/apikeysThe second request,
POST /api/v1/apikeys, answers thehmapi_...token once, and that token is the identity provider's secret.provisionis needed for SCIM's reads as well as its writes.Both requests present the administrator password, which works while no second factor is enrolled on it. With one enrolled, sign in once at
POST /api/v1/sessionwith the code inX-hMailServer-OTP, and send both requests with thehmailsessioncookie andX-Requested-With: hMailServer: since 6.3.4 a one-time code is taken once, so a second request carrying the same code within the same half minute is refused with401.Every other credential is refused. The administrator password, an account's password and a browser session are answered
401on/scim/, in SCIM's shape, withWWW-Authenticate: Bearer.A key confined to named domains (
"domains":"example.com") provisions those domains and learns nothing about the rest: listings are filtered, an account elsewhere is the same404as one that is not there, and an address elsewhere is refused400 invalidValue.A key may not change or remove an account whose authority it does not take in, and is answered
403. A key whose role holdsprovisionalone takes in no administrator at all: not a domain administrator, not a help desk, not an auditor. A full key confined to named domains takes in an administrator of those domains and nobody else. A full key issued for every domain takes in every account.SCIM: the routes and the mapping
Route What it does GET /scim/v2/ServiceProviderConfigWhat this server supports: patch, filter (200 at most), changePassword and etag; not bulk and not sort GET /scim/v2/Schemas,GET /scim/v2/ResourceTypesThe User and Group schemas, listing only what this server keeps; the two resource types GET,POST /scim/v2/UsersThe accounts: listed and filtered, or one made GET,PUT,PATCH,DELETE /scim/v2/Users/{id}One account: read, replaced, changed, removed GET,POST /scim/v2/GroupsThe distribution lists: listed and filtered, or one made GET,PUT,PATCH,DELETE /scim/v2/Groups/{id}One list: read, replaced, changed, removed Every answer under
/scim/isapplication/scim+json, the refusals included.A User is an account.
SCIM This server id,userNameThe account's address. The id is the address, so a rename changes the id. name.givenName,name.familyNameThe first and last names, sixty characters each displayNameThe two names composed. Written alone to an account that already has a name, it is accepted and not kept. activeThe account's switch. falsesuspends it and deletes nothing.emailsThe address, typeworkandprimary, and every alias whose value is the address,typeotherpasswordWrite-only. Judged by the password policy, the reuse history and, when it is on, the breach check. externalIdThe identity provider's own id for the person, at most 255 characters (schema 6096) meta.createdandmeta.lastModifiedare not answered, because the tables keep no such times. A Group is a distribution list:idanddisplayNameare the list's address, andmembersare its members. The sharing groups at/api/v1/groupsare folder-permission principals and are not SCIM groups.Attributes the schemas have and this server does not keep, such as
title,phoneNumbersand the enterprise extension'sdepartmentandmanager, are accepted and not kept. A misspelt attribute or path is refused by name.SCIM: what each operation does
Making a user.
POST /scim/v2/Usersneeds auserNameat a domain this server hosts and the key reaches. A body with nopassword, which is what Entra sends, makes the account with thirty-two random bytes for one, which nobody is ever told. An address something already has is409 uniqueness.Filters. These and no others:
userName eq,userName co,userName sw,externalId eq,emails.value eqandactive eq. Any other filter is400 invalidFilter.countis at most 200, andsortByandsortOrderare ignored. Groups filter ondisplayName eqonly.Changing.
PUTtakes the body aPOSTtakes, and an attribute it leaves out is left as it is.PATCHtakes aPatchOp, withopin any case, as Entra capitalises it.If-Matchis honoured onPUT,PATCHandDELETE; a version other than the current one is412. A change is judged whole before anything is written.Suspending.
activefalserefuses every sign-in from the next attempt, refuses mail to the account, ends its browser sessions and drops its live IMAP, POP3 and SMTP connections. The mailbox, the aliases and the lists are untouched.Renaming. A new
userNamerenames the account through the mailbox move. It is refused409while the mailbox is in use. The id changes, the old id is404from then on, and the old address is kept as an alias until a later request that sendsemailsleaves it out.Every alias of the account is part of
emails, including one an administrator made. An identity provider that replacesemailsremoves the aliases it does not list. Where aliases are managed on this server and not in the directory, do not mapemailsbeyond the work address.Removing.
DELETE /scim/v2/Users/{id}removes the account and its mailbox, every aliasemailsanswered for it, and its row in every distribution list. It answers204. Nothing of it can be put back but from a backup. Entra sendsactivefalsestraight away and theDELETEthirty days after a user is soft-deleted, so the suspension is the safe first step.Five diagnostics in the error log,
HM7220toHM7224, name what a SCIM write left behind when it failed part way. Every SCIM write is on the audit trail, each row namingkey:<id>.Setting up Microsoft Entra ID for SCIM
- Issue the key as above and note the token.
- In the Entra admin centre, Enterprise applications > New application > Create your own application > Integrate any other application you don't find in the gallery (Non-gallery).
- Provisioning > Automatic. Tenant URL:
https://<host>:<port>/scim/v2, where the port isRestApiPort. Secret token: thehmapi_...token. Test connection asks for a user that does not exist and expects an empty list, which is what this server answers. - Mappings > Provision Microsoft Entra ID Users: map
userNamefromuserPrincipalNamewhere that is the mailbox address, or frommailwhere it is not.emails[type eq "work"].valuemust be the same address asuserName. Keepactive,name.givenName,name.familyName,displayNameandexternalId, and delete the mappings this server does not keep. - Mappings > Provision Microsoft Entra ID Groups, for mail-enabled groups that should be distribution lists: map
displayNamefrommail, keepmembers, and delete the mapping ofexternalId. - Assign the users and groups, and switch Provisioning Status on.
Setting up Okta for SCIM
- Issue the key as above.
- Applications > Create App Integration > SAML 2.0 or SWA, then on General enable SCIM provisioning.
- Provisioning > Integration: SCIM connector base URL
https://<host>:<port>/scim/v2; Unique identifier field for usersuserName; Supported provisioning actions: Import New Users and Profile Updates, Push New Users, Push Profile Updates, Push Groups; Authentication Mode HTTP Header, and the token in Authorization. - Provisioning > To App: enable Create Users, Update User Attributes and Deactivate Users.
- Push Groups sends a group under its Okta name as
displayName, and here that is the list's address, so a push ofEngineeringis400 invalidValue. Name each pushed group with the list's address, or leave Push Groups off.
Okta sends
emailswith the primary address alone, so an alias made on this server is removed by Okta's next update. Scim.md gives no steps for Google Workspace: at the time it was written, a custom application there could not be pointed at a SCIM endpoint.Single sign-on with OpenID Connect
Register this server at the issuer as an authorization-code client with PKCE, with the redirect URI
<oidc_redirect_base_url>/portal/oidc/callback.Setting What it is oidc_issuerFor example https://login.example.com. The discovery document is read from<issuer>/.well-known/openid-configurationand cached for an hour. Empty offers no single sign-on at all.oidc_client_idThe client id this server is registered under oidc_client_secretIts secret, stored protected and never emitted. Empty for a public client. oidc_scopesopenid emailby defaultoidc_redirect_base_urlThe address the webmail is reached at from outside. Empty falls back to import_oauth_redirect_base_url.oidc_auto_provisionWhether a verified address with no mailbox here gets one. Off. oidc_administratorsThe addresses that open the Control Deck as the administrator. Empty means single sign-on reaches the webmail only. The flow is
GET /portal/oidc/start[?deck=1]to the issuer andGET /portal/oidc/callbackback. The callback verifies theid_token: its signature against the issuer's published keys (RS256 and ES256 only),iss,aud,exp,iatandnbfwith a minute of skew, and thenonce. It then takesemail, and only whenemail_verifiedsays the issuer verified it.With
oidc_issuerset, both sign-in pages draw a Sign in with button naming the issuer's host. What they draw is decided byGET /api/v1/signin-methods, which needs no credential and says nothing about who exists here. With SAML set up it answerssaml: trueandsaml_label, and both pages draw a second button.The callback does not make the session. It mints a one-use ticket, good for two minutes, and the page hands it to
POST /api/v1/session, which is where every browser session is made. The ticket is bound to the browser by anhmssocookie, andPOST /api/v1/sessionrefuses a ticket that arrives without it.Mapping. The verified address is looked up as a mailbox. With auto-provisioning off, the default, a sign-in with no mailbox is refused. With it on, a mailbox is made, but only at a domain this server already has, and with a random password nobody is told. For the Control Deck, the address must be in
oidc_administrators. An issuer that says somebody is an administrator is not obeyed.Single sign-on with SAML 2.0
For identity providers that speak only SAML 2.0: ADFS above all, and an Entra ID or Okta application set up the SAML way. It ends in the same ticket, the same
POST /api/v1/sessionand the same mapping rules.Setting What it is saml_idp_entity_idThe identity provider's entity ID. Empty, the default, offers no SAML sign-in at all. saml_idp_sso_urlIts sign-in URL for the HTTP-Redirect binding saml_idp_certificateIts signing certificate in PEM, or two while it rolls its key over. Write-only. saml_sp_entity_idThis server's entity ID. Empty means <base URL>/saml.saml_email_attributeThe attribute the address is read from when the NameID is not an email address saml_require_signed_assertionWhether the Assertion must carry its own signature. On by default. saml_auto_provision,saml_administratorsAs the OpenID Connect settings of the same names saml_button_labelThe words on the button: Sign in with SAML by default The base URL is OpenID Connect's:
oidc_redirect_base_url, falling back toimport_oauth_redirect_base_url. From it follow the entity ID<base URL>/saml, the ACS URL<base URL>/portal/saml/acsand the metadata at<base URL>/saml/metadata.Identity provider What to enter there What to copy back Microsoft Entra ID Identifier (Entity ID) <base URL>/saml; Reply URL<base URL>/portal/saml/acs; token encryption offLogin URL to saml_idp_sso_url; Microsoft Entra Identifier tosaml_idp_entity_id; Certificate (Base64) tosaml_idp_certificateOkta Single sign-on URL <base URL>/portal/saml/acs; Audience URI (SP Entity ID)<base URL>/saml; Name ID format EmailAddress; Assertion Encryption UnencryptedIdentity Provider Single Sign-On URL to saml_idp_sso_url; Identity Provider Issuer tosaml_idp_entity_id; X.509 Certificate tosaml_idp_certificateADFS A claims-aware relying party trust, imported from <base URL>/saml/metadataor entered by hand; E-Mail-Addresses sent as E-Mail Addresshttps://<adfs host>/adfs/ls/assaml_idp_sso_url; the Federation Service Identifier assaml_idp_entity_id; the Token-signing certificate assaml_idp_certificateThe flow.
GET /portal/saml/start[?deck=1] to the identity provider, with an AuthnRequest, and the hmsaml cookie POST /portal/saml/acs back from it, with the Response GET /portal/saml/finish on from the ACS, where the cookie is asked for GET /saml/metadata this service provider, for the identity providerThe ACS takes a form of at most 256 KB; a larger body is answered
413. The signature is verified against the certificates an administrator stored and never a key the message carries. The signature method must bersa-sha256orecdsa-sha256, and SHA-1 is refused. A Response is taken only when it answers a request this server issued, not yet answered, with the same RelayState. The checks refuse signature wrapping, a replayed Response or assertion, a Response nobody asked for and an encrypted assertion; comment injection in theNameIDgains nothing, because the address read is the whole of what was signed.A
saml_idp_certificatethat cannot be read stops every SAML sign-in. The settings refuse one, so it comes only from a database edited by hand;HM7320in the error log then names the setting to paste again.A SAML identity provider says nothing like
email_verified. The attribute the address is read from must be one the directory controls, such asmailor the user principal name, and never one a person can set themselves. This server cannot check that.What it does not do
- SCIM: no bulk, no sorting and no
POST .search(/Bulk,/MeandPOST .searchanswer501); no enterprise user extension; no groups but distribution lists; not a SCIM client.externalIdis shown by nothing but SCIM and COM'sAccount.ExternalId, which is Windows-only. - Single sign-on, both kinds: no logout in either direction, so an account disabled at the identity provider keeps a session it already started until that session's own ceiling ends it. One OpenID Connect issuer and one SAML identity provider. Nothing is enforced at IMAP, POP3 and SMTP.
- OpenID Connect: no claims mapping. The
emailclaim and nothing else. - SAML: no encrypted assertions, no signed requests, no metadata fetch, no artifact binding and no sign-in started by the identity provider.
Where to read more
- The operator documents: Scim.md, PasskeysAndSingleSignOn.md and Roles.md.
- The wiki: The REST API and APIs Reference.
- The public documentation: chapter 24, The REST API and chapter 36, The Control Deck and the webmail portal.
- On this forum: passkeys, recovery and the rest of sign-in are in Signing in: passwords, second factor, passkeys, single sign-on; LDAP sign-in and directory synchronisation are in Active Directory and LDAP sign-in; The Control Deck; the 6.3.4 announcement; and the REST API guide.
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login