Repository navigation
Sign in and Identity
Written on 24 September 2026 from
hmailserver/docs/PasskeysAndSingleSignOn.md,Scim.md,AccountRecovery.md,DevicesAndSessions.md,Roles.mdandPasswordBreachCheck.md, and brought up to 6.3.4 (27 September 2026, schema 6104), on 26 September 2026. Apart from the second factor, the password policy and app passwords (§1), everything on this page is new in 6.3.4. Brought up on 5 October 2026 to 6.3.6 (6 October 2026, schema 6108), for what 6.3.6 changes on this page. 6.3.7 (17 October 2026, schema 6108) and 6.3.8, the latest release (2 November 2026, schema 6108), are not yet reflected here.
How people prove who they are to this server, and how the organisation's directory tells it who they are. There are two front doors, and they are not the same door:
- The mail protocols - IMAP, POP3, SMTP submission, and CalDAV and CardDAV - take a password: the account's own, or an app password, or an OAuth2 bearer token where that is switched on. Nothing on this page after §1 is a new way in to them, though a suspension, a new password or a revoked device (§4, §6) does end their live connections.
-
The two pages - the webmail at
/portaland the Control Deck at/- take the password as well, and since 6.3.4 also a passkey, or a sign-in at the organisation's identity provider over OpenID Connect or SAML 2.0. All of them end in the same place:POST /api/v1/session, which mints the one kind of browser session this server has.
flowchart TD
P["Password<br/>(+ one-time code or recovery code)"] --> S["POST /api/v1/session"]
K["Passkey (WebAuthn)"] --> S
O["OpenID Connect issuer"] -->|"one-use ticket"| S
SA["SAML 2.0 identity provider"] -->|"one-use ticket"| S
S --> B["One browser session:<br/>the webmail, or the Deck"]
AP["Password or app password"] --> M["IMAP, POP3, SMTP,<br/>CalDAV, CardDAV"]
IDP["Entra ID, Okta"] -->|"SCIM 2.0 at /scim/v2"| A["Accounts, aliases, lists"]
- What 6.3.3 already has
- Passkeys
- Single sign-on: OpenID Connect and SAML
- SCIM provisioning
- Account recovery
- Devices and the session policies
- Administrator roles
- What none of this does
| Feature | What it is | Where |
|---|---|---|
| A second factor for an account | A TOTP secret on the mailbox. Once one exists, the account's password stops being a credential wherever a code cannot be presented alongside it - every mail client, and the webmail's sign-in - and app passwords are what those use; the Control Panel, over COM, takes the password with the code | The account dialog's Two-factor tab; Connecting a Mail Client |
| App passwords | One per device, shown once, revocable one at a time; work for directory-linked accounts too, and are untouched by password expiry | The account dialog, and the webmail's Security page |
| A second factor for the administrator | Enforced by the server on every surface - COM, REST, the Control Panel | Security Hardening §17.4 |
| The password policy | Length, mixed case, digits, symbols, a common-password list, history and maximum age | Security Hardening §17.2 |
| LDAP and Active Directory | Passwords checked against the directory; provisioning from it on a preview-then-apply | Security Hardening; the Control Panel's Directory pages |
| OAuth2 bearer tokens on the protocols | SASL XOAUTH2 and OAUTHBEARER validated against an identity provider's published keys | Security Hardening §17.2 |
Two things are added to the password itself in 6.3.4: the breach check, which refuses a new password found in the published corpus of breached passwords (checked when a password is chosen, never at logon; off by default; it sends five characters of the password's SHA-1 and nothing else), and recovery codes, which stand in for the one-time code (§5).
Four more things 6.3.4 changes about credentials, each described where it belongs:
-
The administrator password is hashed again at sign-in. The Windows installer wrote it as an
unsalted MD5 digest that nothing ever replaced; the first successful administrator sign-in -
the Control Panel, COM, REST,
hmctlor the Deck - writes it again as PBKDF2, and a new installation's installer writes PBKDF2 itself. A browser session made under the old hash ends once. AhMailServer.inithe service cannot write keeps the old value and says so once (HM7494). -
An account's TOTP secret is sealed at rest once the installation has a stored-secrets key -
made by the Windows installer on a new installation; after an upgrade, and for the Linux packages,
only when an administrator runs
--create-stored-secrets-key(Security Hardening). - A mailbox encrypted at rest takes its own password only: no TOTP secret can be enrolled on it, an account with one cannot be switched to it, and its app passwords are refused (Message Storage, Connecting a Mail Client).
- A meeting room or a piece of equipment cannot be signed into by any credential - password, app password, token, ticket, certificate, passkey or single sign-on; its delegates answer for it (Rooms and Equipment).
Schema 6077. A passkey is a WebAuthn credential held by the person's own device - a phone, a laptop's secure enclave, a security key. The private half never leaves it; the server keeps the public half. Signing in is the device's fingerprint reader, face or PIN, and nothing is typed or sent that could be captured and replayed.
- Enrolling. The webmail's Security page has a Passkeys card; the Control Deck has a Passkeys view for the administrator's own. Both ask for the owner's password again - and the one-time code where one is enrolled - so a session somebody else is holding cannot add a way in that outlives it. At most ten per owner.
- Signing in needs no address typed first: the browser offers whatever passkey it holds for this server. User verification is required on every assertion, not only at enrolment, so a passkey is two factors on its own and stands in for the one-time code.
- It is still the account. A passkey does not carry anybody past an inactive account, the compromised-account hold or the domain's country policy.
-
Behind several names,
passkey_relying_partyset to the registrable parent (example.com) lets a passkey made atmail.example.comwork atwebmail.example.com. Since 6.3.4 it needspasskey_allowed_originsbeside it, the origins this server's pages are served at, comma separated as the browser writes them (https://mail.example.com, https://webmail.example.com); until that is set no passkey is registered or taken. Every registration and sign-in must come from an origin it names, and a credential made in a frame of another site's page is refused: a parent-domain relying party is one a page at any host under it may ask the browser for, and before the audit of 6.3.4 such a page could collect a person's assertion and sign in as them (PasskeysAndSingleSignOn.md). - Not verified: the attestation statement - the manufacturer's word on what kind of device it is. So an installation cannot insist on hardware keys of one make.
- Not in the backup. A passkey is bound to this server's name and to the device; enrol it again after a restore elsewhere.
Since 6.3.6 (item 33) an OpenID Connect start, a SAML start and a passkey sign-in's challenge
leave nothing on the server: the hmoidc cookie holds the sign-in's state, PKCE verifier, nonce,
page and end, hmsaml its request's ID, and hmpasskey its challenge and end, each sealed with
AES-256-GCM under a key the server process makes for itself, holds in memory and makes again
whenever the REST listener stops. 6.3.5 held tables of 200, 200 and 1,000 that dropped their
oldest whoever held them, so a client asking for sign-in starts a few times a second made every
other person's sign-in fail; no number of starts can now push anybody's sign-in out. A sign-in
is spent once it finishes, and a second answer to it is refused; one started before the listener
stopped is not finished after it ("this sign-in was not started here"); two servers behind one
name each seal with their own key. X-Forwarded-For is not read for any of this. A passkey
registration's challenge counts against the credential that asked for it, fifty a credential
(PasskeysAndSingleSignOn.md,
What a stranger can make the server hold).
Both point the two pages' sign-in at the organisation's identity provider; the verified address
names a mailbox here, and a one-use ticket good for two minutes carries it to
POST /api/v1/session. Neither does anything until an administrator names a provider, and the
password stays as the fallback. The settings are REST's and the Deck's (its Server group); there
is no Control Panel page and no COM property for either.
| OpenID Connect | SAML 2.0 | |
|---|---|---|
| For | Any OIDC issuer: Entra ID, Okta, Google, Keycloak | ADFS, and Entra ID or Okta set up the SAML way |
| Register this server as | An authorization-code client with PKCE, redirect URI <base URL>/portal/oidc/callback
|
A service provider: entity ID <base URL>/saml, ACS <base URL>/portal/saml/acs, metadata at <base URL>/saml/metadata
|
| Settings |
oidc_issuer, oidc_client_id, oidc_client_secret (write-only), oidc_scopes, oidc_redirect_base_url
|
saml_idp_entity_id, saml_idp_sso_url, saml_idp_certificate (one or two, while the provider rolls its key), saml_sp_entity_id, saml_email_attribute, saml_require_signed_assertion (on), saml_button_label
|
| The address comes from |
email, only when email_verified says the issuer verified it |
The NameID in email format, or the attribute saml_email_attribute names - which must be one the directory controls, since SAML has no email_verified
|
| What is verified | The id_token's RS256 or ES256 signature against the issuer's published keys, iss, aud, expiry and nonce
|
The XML signature, by an XML reader, Exclusive Canonicalization and XML-Signature verifier written for this and held to the W3C's examples; signature wrapping, replay, comment injection, unsolicited and encrypted assertions all refused |
| Who opens the Deck | Addresses in oidc_administrators
|
Addresses in saml_administrators
|
| Unknown address | Refused, unless oidc_auto_provision is on - then a mailbox is made at a domain this server already has, with a random password nobody is told |
The same, with saml_auto_provision
|
An identity provider that says somebody is an administrator is not obeyed. Who may do what here is this server's roles and this server's lists. Neither kind does logout in either direction: a person disabled at the provider keeps a session already started here until its own ceiling ends it (set the session policies, §6). There is no claims mapping - no groups, no display names - and one provider of each kind. SAML also has no signed requests, no encrypted assertions and no IdP-initiated sign-in: a person starts from the sign-in page.
Schema 6096. The other direction: the organisation's identity provider - Entra ID, Okta -
calls this server at /scim/v2 on the REST listener (SCIM 2.0, RFC 7643 and 7644) and makes,
renames, suspends and removes accounts, with their aliases and distribution lists, as people join,
move and leave. The LDAP backend authenticates; SCIM provisions. Single sign-on's
auto-provisioning makes a mailbox in passing and never touches it again; SCIM keeps exactly the
people the organisation assigned to this server.
| SCIM | This server |
|---|---|
| A User | An account; userName is its address, active its switch, name its first and last names, emails its address and its aliases, externalId the provider's own id |
| A Group | A distribution list; members its members |
active: false |
Suspends: every sign-in refused, browser sessions ended, live IMAP, POP3 and SMTP connections dropped, mail to it refused - nothing deleted. Entra sends this at once and the DELETE thirty days later |
A new userName
|
A rename through the mailbox move: the store follows, the old address is kept as an alias |
DELETE |
The account, its mailbox, its aliases and its rows in every list |
A new password
|
Judged by the password policy and the breach check; ends the account's sessions |
Turning it on. Make a role that holds the provision permission alone, issue an API key
with that role (and, if you like, confined to named domains), and give the provider
https://<host>:<RestApiPort>/scim/v2 and the hmapi_... token. A key may never change an
account whose authority reaches further than its own. Every write is on the audit trail under the
key. Not done: bulk operations, sorting, the enterprise user extension (a department or a
manager is accepted and not kept), and sharing groups - a SCIM group is a distribution list, never
a folder-permission group. Scim.md
has the Entra ID and Okta mappings step by step.
Schema 6076. What a forgotten password and a lost authenticator used to be: a ticket. Now a person sets, on the webmail's Security page:
- a recovery address - another mailbox of theirs, which counts for nothing until a link sent to it is followed, and which is never shown in full again, to anybody;
- ten one-time recovery codes, shown once, stored hashed, each good once, standing in for the authenticator's code wherever one is asked for - at sign-in, at a password change, when minting an app password.
Forgotten your password? on the sign-in card then sends a single-use half-hour link to the
confirmed recovery address and nowhere else, answers the same sentence for every address so that
it is no address directory, and on completion applies the password policy, the reuse history and
the breach check, ends every browser session, tells the mailbox and the recovery address, and
raises the account.password-reset alert. An administrator sees whether an address is set and
confirmed and how many codes are left, and can clear either - on the Control Panel's
Recovery tab, the Deck and REST - but can read neither.
It needs a public base URL for the links (confidential_base_url, or the quarantine digest's, or
the import sign-in's); account_recovery_enabled is on and inert until then. A recovery
address is exactly as strong as the mailbox that receives it: it is a convenience, not a
second factor. And it does not enrol a new authenticator: somebody who lost theirs gets back in
with a code and asks an administrator to enrol the second factor again.
Schema 6053. What has signed in as me? Every client that has authenticated to an account is
one entry: the protocol, the address, the program where the protocol names one, which credential
it used (password, app_password, passkey, single_sign_on), and when it was first and last
seen. The person reads it on the webmail's Security page; an administrator on the Deck's
Devices view, over REST, COM, hmctl and PowerShell. Revoking an entry ends its browser
session and its live IMAP, POP3 and SMTP connections, and can turn off the app password it
used - what to do about a stolen phone.
Four session policies govern every browser session - the webmail and the Deck - server-wide and per domain, re-read at every request:
| Policy | Unset means |
|---|---|
Idle timeout (session_idle_seconds) |
30 minutes |
Absolute lifetime (session_lifetime_seconds) |
12 hours |
| One session per device | off |
| Bound to the address it began on | off - it ends a phone's session when it moves from wi-fi to mobile |
Nothing changes on the day of the upgrade: every policy reads 0, which means the ceilings the server has always had.
Schema 6047. Who may administer what, without handing out the administrator password. Four built-in roles, which cannot be edited, and custom ones:
| Role | For |
|---|---|
serveradmin |
Everything, as the administrator password always could |
domainadmin |
The accounts, aliases, lists and mailboxes of a domain |
helpdesk |
Finding an account, resetting its password, unlocking it, and reading where its mail went - nothing else |
auditor |
Reading everything, the audit trail included, and changing nothing |
Since 6.3.4 the vocabulary has twenty-seven permissions. The last three came late in the
release: view_discovery, which the auditor holds, and manage_discovery, for eDiscovery cases
(Compliance and eDiscovery); and manage_inbound_webhooks, which only serveradmin holds,
for routing mail to a URL (Mail In to a Webhook). Neither of the two write permissions belongs
to any other built-in role; a custom role may be given them.
A role is assigned to an account in named domains, on Accounts & domains → Administrator
roles, the Deck's Roles view, REST or COM; the account then signs in to the Control Deck with
its own address and password and sees what its role allows. API keys carry a role too - which is
how a SCIM key holds provision and nothing else, and how a send-API key holds
send_through_api. An account with no assignment keeps exactly the authority its admin level
always gave it. Every change made under a role is on the audit trail with [role: <name>].
- It does not reach the mail protocols. A phone's IMAP and SMTP still take the password or an app password. An account that only ever signs in through its identity provider has a random password; it needs one set before its person can mint an app password for a phone.
- No logout from the identity provider, and no single logout. Set the session policies to bound how long a session outlives a person's removal at the provider.
- No password-less accounts. A passkey is a way in beside the password, not instead of it; there is no policy that forbids the password.
- No self-service enrolment of an authenticator app in the webmail; an administrator enrols the second factor.
- One OpenID Connect issuer and one SAML identity provider.
See also: Security Hardening · The Webmail · The Control Deck · Connecting a Mail Client · The REST API
hMailServer 6.3.8, the latest release (2 November 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.8 is the latest release (2 November 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page