<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Active Directory and LDAP sign-in, directory synchronisation and OAuth2 bearer tokens]]></title><description><![CDATA[<p dir="auto"><em>Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) changes three things this guide relies on. A Control Panel signed in to a server on another computer no longer reads or writes its own computer's <code>hMailServer.ini</code> for the <code>[LDAP]</code> values, which it cannot read from the server, where it showed that file or the defaults as the server's; the sidebar is regrouped, and Ctrl+K takes every old page title to its page. With SMTP logging on, 6.3.6 and every earlier version that offered them wrote the bearer token of <code>AUTH XOAUTH2</code> and <code>AUTH OAUTHBEARER</code> and the lines of SCRAM-SHA-256, OAUTHBEARER and GSSAPI into the SMTP log in the clear; 6.3.7 hides every line of a SASL exchange but the user name of <code>AUTH LOGIN</code> and the authentication identity of <code>AUTH PLAIN</code>. An account given the <code>serveradmin</code> role with a list of domains is, over COM as over REST, confined to those domains and no longer the server's administrator, so a script that runs the directory synchronisation's preview and apply as such an account needs the role given with no list. See <a href="/topic/28277">the 6.3.7 announcement</a>. hMailServer 6.3.8 (2 November 2026) signs a name in to an account only when it is the account's stored address, compared in lower case after a domain alias and the default domain, for the name an OAuth2 bearer token carries as for a password (work item 104), so a name that differs from the stored address in more than the case of the letters A to Z, or has white space at either end, may no longer sign in. On Windows an <code>OAuth2PublicKeyFile</code> whose path holds characters outside ASCII may be refused with <code>HM7536</code> (one outside the code page, which worked up to 6.3.7, is refused), and then the key verifies no token: give the file, and the folders above it, an ASCII name before upgrading. See <a href="/topic/28278">the 6.3.8 announcement</a>.</em></p>
<p dir="auto">This topic covers three ways an organisation's directory or identity provider vouches for somebody on hMailServer 6.3.4's mail protocols: checking a password against Active Directory or another LDAP directory, creating mailboxes from that directory, and accepting OAuth2 bearer tokens. <strong>Know this first:</strong> 6.3.4 keeps settings in the database, but the <code>[LDAP]</code> section stays in <code>hMailServer.ini</code> on each server, with no REST route and no environment name; the server re-reads it within two seconds of a change, with no restart. The OAuth2 keys are ordinary <code>[Settings]</code> keys, stored like any other.</p>
<h3><a class="anchor-offset" name="three-mechanisms-three-questions"></a>Three mechanisms, three questions</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Mechanism</th>
<th>The question it answers</th>
<th>Control Panel page (Windows)</th>
</tr>
</thead>
<tbody>
<tr>
<td>LDAP directory authentication</td>
<td>Is this the password of the directory user the account is linked to?</td>
<td><strong>Access &amp; abuse protection → Directory authentication (LDAP)</strong></td>
</tr>
<tr>
<td>Directory synchronisation</td>
<td>Which mailboxes should exist, according to the directory?</td>
<td><strong>Access &amp; abuse protection → Directory synchronisation</strong></td>
</tr>
<tr>
<td>OAuth2 bearer tokens</td>
<td>Did the identity provider sign this token, and which mailbox does it name?</td>
<td><strong>Access &amp; abuse protection → Authentication</strong></td>
</tr>
</tbody>
</table>
<p dir="auto">All three are off by default, and an upgrade switches none of them on: the <code>[LDAP]</code> section is absent until you add it, and <code>OAuth2Enabled</code> defaults to 0.</p>
<h3><a class="anchor-offset" name="where-the-code-ldap-code-section-lives"></a>Where the <code>[LDAP]</code> section lives</h3>
<ul>
<li><strong>Windows.</strong> The Control Panel's page writes the <code>hMailServer.INI</code> of its own machine. Connected to another machine, its Save is disabled, although "the connection test still works with the values typed here". Work on the server, over Remote Desktop.</li>
<li><strong>Linux.</strong> No Control Panel and no COM. The file is <code>/etc/hmailserver/hMailServer.ini</code>, <code>0640 root:hmailserver</code>: <code>sudoedit /etc/hmailserver/hMailServer.ini</code>.</li>
<li><strong>Not REST or <code>hmctl</code>.</strong> The REST ini routes and <code>hmctl ini set</code> handle <code>[Settings]</code> keys only.</li>
<li><strong>Per server.</strong> On a warm standby each node has its own <code>[LDAP]</code>; keep the two alike by hand.</li>
<li><strong><code>ServicePassword</code></strong> is a line of that file. It is not among the <code>[Settings]</code> secrets 6.3.4 seals under the stored-secrets key; the Control Panel's field for it is write-only.</li>
</ul>
<h3><a class="anchor-offset" name="the-sign-in-keys"></a>The sign-in keys</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Key</th>
<th>Default</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Enabled</code></td>
<td>0</td>
<td>The master switch</td>
</tr>
<tr>
<td><code>Server</code></td>
<td>empty</td>
<td>The directory host; required</td>
</tr>
<tr>
<td><code>Port</code></td>
<td>0</td>
<td>0 means 636 for LDAPS, 389 otherwise</td>
</tr>
<tr>
<td><code>Security</code></td>
<td>2</td>
<td>2 LDAPS, 1 StartTLS, 0 plain; anything else is LDAPS</td>
</tr>
<tr>
<td><code>VerifyCertificate</code></td>
<td>1</td>
<td>Check the directory's certificate</td>
</tr>
<tr>
<td><code>AllowUnprotectedPassword</code></td>
<td>0</td>
<td>Permit a simple bind over a plain connection</td>
</tr>
<tr>
<td><code>BindMethod</code></td>
<td>0</td>
<td>0 simple; 1 Negotiate (Kerberos or NTLM through Windows SSPI)</td>
</tr>
<tr>
<td><code>SearchBase</code></td>
<td>empty</td>
<td>Required unless <code>UserDnTemplate</code> is set or the bind is Negotiate</td>
</tr>
<tr>
<td><code>UserSearchFilter</code></td>
<td><code>(&amp;(objectCategory=person)(objectClass=user)(sAMAccountName=%u))</code></td>
<td>How the user's entry is found</td>
</tr>
<tr>
<td><code>UserDnTemplate</code></td>
<td>empty</td>
<td>Bind directly, without searching</td>
</tr>
<tr>
<td><code>ServiceUsername</code>, <code>ServiceDomain</code>, <code>ServicePassword</code></td>
<td>empty</td>
<td>What the search binds with</td>
</tr>
<tr>
<td><code>TimeoutSeconds</code></td>
<td>10</td>
<td>Bounds every directory operation; 1 to 90</td>
</tr>
<tr>
<td><code>FallbackToWindowsLogon</code></td>
<td>0</td>
<td>Also try Windows <code>LogonUser</code>; Windows only</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>LDAPS with <code>VerifyCertificate=1</code> is the default and the recommended configuration</strong>, proven end to end against a Windows Server 2025 domain controller. <code>Server</code> must be the name in the directory's certificate: the same setup pointed at the controller's IP address, with the right password, is refused, and <code>HM5920</code> reports a failure "unrelated to the credentials supplied". On Windows, put the issuing CA in <code>LocalMachine\Root</code>; when this was proven the service ran as LocalSystem, and a <code>CurrentUser\Root</code> import served it nothing. The bind needs no extra privilege under <code>NT SERVICE\hMailServer</code>, which a new 6.3.4 installation runs as. On Linux the certificate must chain to the system trust store.</p>
<p dir="auto"><strong>StartTLS</strong> (<code>Security=1</code>) connects on 389 and upgrades before anything is sent. <strong>Plain LDAP</strong> is labelled <em>Unprotected LDAP - cleartext (lab networks only)</em>: there a simple bind is refused (<code>HM5921</code>) rather than sent, unless <code>AllowUnprotectedPassword</code> is 1.</p>
<p dir="auto"><strong>Simple or Negotiate.</strong> A simple bind sends the password to the directory, so it belongs on LDAPS or StartTLS. Negotiate, Windows only, never sends it; it worked on 389 against a controller that had no LDAPS certificate yet, and suits a mail server not joined to the domain.</p>
<p dir="auto"><strong>Search or template.</strong> A search binds with the service credentials, finds the user under <code>SearchBase</code> with <code>UserSearchFilter</code>, then binds as the user. It asks for two entries to see ambiguity: more than one match is a configuration fault and authenticates nobody. <code>UserDnTemplate</code> skips the search, so no service credential need be stored at all; with a simple bind, that is the configuration the feature was proven with.</p>
<h3><a class="anchor-offset" name="linking-an-account-to-the-directory"></a>Linking an account to the directory</h3>
<p dir="auto">The directory is asked only about linked accounts; others keep their own password. The webmail's sign-in shares the IMAP and POP3 logon, so it takes the directory password too.</p>
<ul>
<li><strong>Control Panel:</strong> the account dialog's <strong>Windows directory</strong> tab, with the domain (<code>ADDomain</code>) and user name (<code>ADUsername</code>); <strong>Browse Active Directory</strong> searches the forest to fill them. Since 6.3.4 the tab has that name; it was called Directory, and the new <strong>Directory entry</strong> tab is the organisation's address book, unrelated to Active Directory.</li>
<li><strong>REST:</strong> <code>PUT /api/v1/accounts/{address}</code> with <code>ad_enabled</code>, <code>ad_domain</code> and <code>ad_username</code>.</li>
<li><strong>COM (Windows):</strong> <code>IsAD</code>, <code>ADDomain</code> and <code>ADUsername</code>.</li>
</ul>
<p dir="auto"><strong>What a link changes.</strong> The account's stored password is no fallback: in the proof run, a linked account's local password was refused. Linked accounts bypass local hashing, so they are exempt from the minimum-hash policy and from password expiry, and cannot sign in with SCRAM-SHA-256; their clients use <code>PLAIN</code> or <code>LOGIN</code> over TLS. App passwords work for them: the directory holds the password, but an app password is local, which is what makes a second factor possible for a directory account at all. The person cannot change the password in the webmail (<code>POST /api/v1/me/password</code> answers 409), and <em>Forgotten your password?</em> cannot reset it, because this server does not hold it.</p>
<p dir="auto"><strong>Without LDAP, on Windows</strong>, a linked account is checked by <code>LogonUser</code>, which needs the mail server's host joined to the domain. On a host that is not joined, Windows answers every domain name, real or misspelt, as a wrong password, so the account can never sign in; <code>HM5911</code> says so, naming the domain and the computer. An empty domain, <code>.</code> or the computer's own name means a <strong>local Windows account</strong>. <strong>Never leave the domain empty by accident:</strong> with <code>[LDAP] Enabled</code> off, an account linked with an empty domain and the user name <code>administrator</code> would accept the server's local Administrator password.</p>
<p dir="auto"><strong><code>FallbackToWindowsLogon</code></strong> never applies to a wrong password: a directory's rejection deliberately does not fall through to <code>LogonUser</code>, because that would double every failed attempt and trip a lockout policy on a single wrong password. Leave it off on a host that is not domain-joined.</p>
<h3><a class="anchor-offset" name="setting-it-up-on-windows"></a>Setting it up on Windows</h3>
<ol>
<li>For LDAPS or StartTLS, put the directory's issuing CA in <code>LocalMachine\Root</code> on the mail server.</li>
<li>On the mail server, open <strong>Directory authentication (LDAP)</strong>. Switch directory authentication on (<code>Enabled</code>); until then a linked account is checked by Windows <code>LogonUser</code>, not with these settings. Fill in the server, the security, the bind, and either the search or <code>UserDnTemplate</code>; the first card asks <em>Is this configuration complete?</em></li>
<li><strong>Test the connection</strong> with a directory user name, its AD domain and its password. It tries the values typed on the page.</li>
<li>Save. The server re-reads <code>[LDAP]</code> within two seconds.</li>
<li>Link one account and sign in with the right password and a wrong one. Over POP3 the proof run got <code>+OK Mailbox locked and ready</code> and <code>-ERR</code>, and the log showed the bind and the rejection separately.</li>
<li>Link the others by hand, or with directory synchronisation.</li>
</ol>
<h3><a class="anchor-offset" name="setting-it-up-on-linux"></a>Setting it up on Linux</h3>
<p dir="auto">The only directory path is an LDAP simple bind over LDAPS or StartTLS, proven against OpenLDAP's slapd.</p>
<ol>
<li>Make sure the directory's certificate chains to the system trust store, and use the name it carries.</li>
<li>Add the section with <code>sudoedit /etc/hmailserver/hMailServer.ini</code>, filling in the angle brackets. Without a <code>UserSearchFilter</code> line the search uses Active Directory's filter, <code>(&amp;(objectCategory=person)(objectClass=user)(sAMAccountName=%u))</code>, which finds nobody in another directory such as OpenLDAP; there, write a filter that matches its user entries, with <code>%u</code> for the name:</li>
</ol>
<pre><code class="language-ini">[LDAP]
Enabled=1
Server=&lt;the host name the directory's certificate carries&gt;
Security=2
VerifyCertificate=1
BindMethod=0
SearchBase=&lt;the DN the users are under&gt;
UserSearchFilter=&lt;that filter, unless the directory is Active Directory&gt;
ServiceUsername=&lt;the search account&gt;
ServicePassword=&lt;its password&gt;
</code></pre>
<ol start="3">
<li>Two seconds later the server has it. Link an account over REST (<code>ad_enabled</code>, <code>ad_domain</code>, <code>ad_username</code>) and sign in with the right password and a wrong one.</li>
</ol>
<p dir="auto">Linux has no Negotiate (<code>BindMethod=1</code> is refused as a directory failure, not a wrong password: <code>HM6420</code>, reported once), no <code>LogonUser</code> (a linked account while <code>[LDAP] Enabled</code> is off is refused with <code>HM6421</code>, which names the settings to set; <code>FallbackToWindowsLogon</code> refuses with <code>HM6404</code>), and no GSSAPI bind.</p>
<h3><a class="anchor-offset" name="when-the-directory-cannot-answer"></a>When the directory cannot answer</h3>
<p dir="auto">So that an outage does not read as users mistyping, infrastructure failures are reported apart from credential rejections, throttled per error and endpoint, and <code>TimeoutSeconds</code> bounds the wait.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Code</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>HM5920</code></td>
<td>The directory could not be used (a certificate not matching <code>Server</code>, say). Reported at High: unrelated to the credentials, and every account on that directory is affected</td>
</tr>
<tr>
<td><code>HM5921</code></td>
<td>A simple bind would have sent the password unprotected, so the logon was refused first</td>
</tr>
<tr>
<td><code>HM5911</code></td>
<td>Windows <code>LogonUser</code>: the host is not joined to the domain; at most once a minute per problem</td>
</tr>
</tbody>
</table>
<h3><a class="anchor-offset" name="directory-synchronisation-preview-then-apply"></a>Directory synchronisation: preview, then apply</h3>
<p dir="auto">Synchronisation reads the directory, works out which mailboxes should exist, and only when asked creates and updates them; it was verified end to end against a Windows Server 2025 domain controller. The planner writes nothing and the applier decides nothing, so a preview cannot promise what the apply would not do.</p>
<ul>
<li><strong>Per-domain opt-in.</strong> A domain takes part only if its Active Directory domain name is set (<code>ADDomainName</code> in the domain dialog, <code>ad_domain_name</code> over REST), so a search base one level too high cannot provision into unrelated hosted domains.</li>
<li><strong>Nothing is ever deleted</strong>, account or message. The most it does is clear <code>Active</code>, only when asked, and never on a truncated or empty enumeration or for a domain in which nothing was seen.</li>
<li><strong>No password.</strong> A made account is marked <code>IsAD</code> and signs in through the directory from the start.</li>
<li><strong>No existing account claimed.</strong> An account that is not linked, or is linked to another AD domain, is reported, not changed.</li>
<li><strong>Paged.</strong> Active Directory answers a search beyond <code>MaxPageSize</code> (1000) with the first page and a success status, so the enumeration pages; <code>SyncMaxUsers</code> is the ceiling.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th><code>[LDAP]</code> key</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>SyncFilter</code></td>
<td><code>(&amp;(objectClass=user)(objectCategory=person)(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))</code></td>
</tr>
<tr>
<td><code>SyncMailAttribute</code></td>
<td><code>mail</code>; <code>proxyAddresses</code> works, preferring the capitalised <code>SMTP:</code> primary</td>
</tr>
<tr>
<td><code>SyncUsernameAttribute</code>, <code>SyncDisplayNameAttribute</code></td>
<td><code>sAMAccountName</code>, <code>displayName</code></td>
</tr>
<tr>
<td><code>SyncMaxUsers</code></td>
<td>5000; at most 100000</td>
</tr>
<tr>
<td><code>SyncScheduleMinutes</code></td>
<td>0, off; 15 at least, 10080 (seven days) at most</td>
</tr>
</tbody>
</table>
<p dir="auto">On Windows:</p>
<ol>
<li>Get authentication working first. Synchronisation binds as the service account, honouring <code>BindMethod</code>, so Negotiate works where there is no LDAPS certificate.</li>
<li>Set the Active Directory domain name on each mail domain that takes part.</li>
<li>Open <strong>Directory synchronisation</strong>, check the selection keys, choose the domain and whether absent accounts are marked inactive, and run <strong>Preview</strong>. It changes nothing.</li>
<li>Read the report, including the existing accounts it reports and leaves alone.</li>
<li><strong>Apply</strong>. The button stays disabled until a preview of the same options has run.</li>
</ol>
<p dir="auto">Behind the page are <code>Settings.PreviewDirectorySync(DomainName, DisableMissing)</code> and <code>Settings.ApplyDirectorySync</code>, for a server administrator and callable from a script. From a Control Panel on another machine the six selection boxes are read-only and, up to 6.3.6, show their defaults; since 6.3.7 the Control Panel no longer reads its own computer's file for the <code>[LDAP]</code> values, which it cannot read from the server, so it no longer shows them as the server's. Preview and Apply still run inside the server.</p>
<p dir="auto"><strong>The unattended schedule</strong> (<code>SyncScheduleMinutes</code>) runs at start and then every so many minutes. It creates and updates but <strong>never disables</strong>: unattended is where nobody reads the report that would show a mailbox about to be taken away. The schedule is one of the tasks the server registers as it starts, and only if <code>SyncScheduleMinutes</code> is set then, so restart the service once after first setting it. <strong>On Linux</strong>, with no COM, there is no preview or apply, and whether the schedule runs there is not documented: treat it as untested.</p>
<h3><a class="anchor-offset" name="oauth2-bearer-tokens-on-imap-pop3-and-smtp"></a>OAuth2 bearer tokens on IMAP, POP3 and SMTP</h3>
<p dir="auto">With <code>OAuth2Enabled=1</code>, SMTP, IMAP and POP3 offer <code>XOAUTH2</code> and <code>OAUTHBEARER</code> (RFC 7628), by default only over TLS (<code>OAuth2RequireTLS=1</code>). The server verifies the token's signature itself, takes the login name from the claim <code>OAuth2UsernameClaim</code> names (<code>email</code>), and signs in to that mailbox; a <code>user=</code> the client asserts must match. <strong>The mailbox must already exist</strong>: a token never makes one.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Key</th>
<th>Default</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>OAuth2JwksUrl</code></td>
<td>empty</td>
<td>The provider's JWK Set; the token's <code>kid</code> picks the key. https, or plain http to loopback only</td>
</tr>
<tr>
<td><code>OAuth2JwksCacheSeconds</code></td>
<td>3600</td>
<td>How long the set is kept</td>
</tr>
<tr>
<td><code>OAuth2PublicKeyFile</code></td>
<td>empty</td>
<td>A PEM RSA or EC public key; the fallback</td>
</tr>
<tr>
<td><code>OAuth2HmacSecret</code></td>
<td>empty</td>
<td>The HS256 key</td>
</tr>
<tr>
<td><code>OAuth2AllowedAlgorithms</code></td>
<td><code>RS256</code></td>
<td>HS256, RS256 and ES256 recognised; <code>none</code> never</td>
</tr>
<tr>
<td><code>OAuth2Issuer</code>, <code>OAuth2Audience</code></td>
<td>empty</td>
<td>Expected <code>iss</code> and <code>aud</code>; empty turns the check off</td>
</tr>
<tr>
<td><code>OAuth2IntrospectionUrl</code></td>
<td>empty</td>
<td>RFC 7662 endpoint asked whether a verified token is still active</td>
</tr>
<tr>
<td><code>OAuth2IntrospectionClientId</code>, <code>OAuth2IntrospectionClientSecret</code></td>
<td>empty</td>
<td>Who this server introspects as, over HTTP Basic</td>
</tr>
<tr>
<td><code>OAuth2IntrospectionCacheSeconds</code></td>
<td>300</td>
<td>Verdict reuse, never past the token's <code>exp</code></td>
</tr>
<tr>
<td><code>OAuth2IntrospectionFailOpen</code></td>
<td>0</td>
<td>1 accepts a token the endpoint cannot judge, logging each</td>
</tr>
</tbody>
</table>
<p dir="auto">An unknown <code>kid</code>, which is what a rotation looks like, fetches the set again at once, at most once a minute; a failed fetch keeps the keys held. <code>HttpProxy</code>, when set, carries these fetches.</p>
<p dir="auto"><strong>Where to set them.</strong> Each applies when saved: on the Control Panel's <strong>Authentication</strong> page, with <code>PUT /api/v1/settings/ini/{name}</code> and the administrator password, with <code>hmctl ini set</code>, or with COM <code>Settings.SetIniSetting</code>. Not in <code>hMailServer.ini</code>: since 6.3.4 the database holds them, and a line edited in the file is not used; <a href="/topic/28222">Settings: when a save takes effect</a> explains the store.</p>
<ol>
<li>Choose the key source: <code>OAuth2JwksUrl</code>, so a rotation needs no copied file; or <code>OAuth2PublicKeyFile</code>; or <code>OAuth2HmacSecret</code> with HS256 allowed.</li>
<li>Set <code>OAuth2Issuer</code> and <code>OAuth2Audience</code> to what the provider writes into its tokens. Left empty, neither is checked: any token the provider's keys signed is accepted, whatever client it was issued to.</li>
<li>Optionally set introspection, so a revoked token stops working before it expires.</li>
<li>Only then set <code>OAuth2Enabled</code> to 1.</li>
<li>On Windows, read the <strong>OAuth2 / XOAUTH2</strong> item of the Control Panel's <strong>External setup</strong> list.</li>
</ol>
<p dir="auto"><strong>Why that order.</strong> Switched on without usable key material, the mechanisms are still advertised, every token fails, and failed bearer sign-ins feed the auto-ban, so a correct client can have its address banned.</p>
<p dir="auto">On Linux before the REST listener is up, <code>hmailserver --set-setting</code> stores a key; a secret is given by name alone and typed at a prompt. A value stored this way reaches the running server at its next save, reinitialise or restart. Set the issuer and audience too, as step 2 says, putting your provider's values in place of these examples:</p>
<pre><code class="language-sh">sudo hmailserver --set-setting OAuth2JwksUrl=https://idp.example.com/keys
sudo hmailserver --set-setting OAuth2Issuer=https://idp.example.com
sudo hmailserver --set-setting OAuth2Audience=hmailserver
sudo hmailserver --set-setting OAuth2Enabled=1
sudo systemctl restart hmailserver
</code></pre>
<p dir="auto"><strong>Since 6.3.4:</strong> a bearer token meets the domain's and the account's country policy, as a password does (a stolen token used to sign in from a refused country); an SMTP session signed in with a token can send more than one message (the second used to be refused with 550 "Login credentials no longer valid"); and <code>OAuth2HmacSecret</code> and <code>OAuth2IntrospectionClientSecret</code> are sealed at rest once the installation has a stored-secrets key, which an upgrade does not make until an administrator runs <code>--create-stored-secrets-key</code>.</p>
<p dir="auto"><strong>Tested how.</strong> The regression fixtures play the identity provider on the loopback, to make it misbehave: RS256 and ES256, the JWK Set and its rotation, introspection and fail-open. A real provider's tokens are not yet part of them.</p>
<p dir="auto">This is inbound only. The <code>OutboundOAuth2</code> keys and <code>FetchOAuth2Hosts</code> are the server's own token for relaying through and collecting from Microsoft 365; <a href="/topic/28269">Routes, relays and smart hosts</a> covers relaying with it.</p>
<h3><a class="anchor-offset" name="where-these-end-scim-single-sign-on-and-kerberos"></a>Where these end: SCIM, single sign-on and Kerberos</h3>
<p dir="auto"><strong>The LDAP backend authenticates; SCIM provisions.</strong> Synchronisation reads the directory; SCIM is the other direction, Entra ID or Okta calling <code>/scim/v2</code> under an API key whose role holds <code>provision</code>, and it removes accounts, which synchronisation never does. Single sign-on, OpenID Connect or SAML 2.0, opens the webmail and the Control Deck only, ending in <code>POST /api/v1/session</code>; the protocols' bearer tokens are a separate, older mechanism with the <code>OAuth2</code> settings above, and single sign-on does not reach mail clients. On Windows, SASL <code>GSSAPI</code> (<code>GssapiEnabled</code>) lets a mail client sign in with a Kerberos ticket, mapped to the account linked to that directory user. <a href="/topic/28267">SCIM provisioning and single sign-on</a> sets those up; <a href="/topic/28253">Signing in</a> and <a href="/topic/28255">Connecting a mail client</a> show the person's side.</p>
<h3><a class="anchor-offset" name="what-it-does-not-do"></a>What it does not do</h3>
<ul>
<li><strong>No Negotiate, Windows logon or GSSAPI bind on Linux.</strong> <a href="/topic/28189">hMailServer on Linux</a> lists the rest.</li>
<li><strong>No LDAP server here</strong>, and the webmail's organisation directory is typed in, not imported from Active Directory.</li>
</ul>
<h3><a class="anchor-offset" name="where-to-read-more"></a>Where to read more</h3>
<ul>
<li>Wiki: <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Settings-Reference" rel="nofollow ugc">Settings Reference</a> sections 9.2 and 11, <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Capabilities-and-Configuration" rel="nofollow ugc">Capabilities and Configuration</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Control-Panel-Reference" rel="nofollow ugc">Control Panel Reference</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Installing-on-Linux" rel="nofollow ugc">Installing on Linux</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Security-Hardening" rel="nofollow ugc">Security Hardening</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Sign-in-and-Identity" rel="nofollow ugc">Sign-in and Identity</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/wikis/Architecture" rel="nofollow ugc">Architecture</a></li>
<li>Documents: <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingsApply.md" rel="nofollow ugc">SettingsApply.md</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/ServiceAccount.md" rel="nofollow ugc">ServiceAccount.md</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Scim.md" rel="nofollow ugc">Scim.md</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/PasskeysAndSingleSignOn.md" rel="nofollow ugc">PasskeysAndSingleSignOn.md</a>, <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/TestLab.md" rel="nofollow ugc">TestLab.md</a></li>
<li>The <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/Roadmap.md" rel="nofollow ugc">Roadmap</a> rows on LDAP, which record how each part was proven</li>
<li>On this forum: <a href="/topic/28241">the stored-secrets key</a>, <a href="/topic/28239">auto-ban</a> and <a href="/topic/28232">the warm standby</a></li>
<li>The public documentation's <a href="https://www.progressiverobot.com/hmailserver-documentation/#64-creating-accounts-from-active-directory" rel="nofollow ugc">Creating accounts from Active Directory</a> and <a href="https://www.progressiverobot.com/hmailserver-documentation/#172-modern-authentication" rel="nofollow ugc">Modern authentication</a></li>
</ul>
<p dir="auto">Report a security flaw in any of this by <a href="https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/SECURITY.md" rel="nofollow ugc">SECURITY.md</a>'s routes: a <a href="https://gitlab.com/hmailserver/hmailserver/-/issues/new?issue%5Bconfidential%5D=true" rel="nofollow ugc">confidential GitLab issue</a>, or email to <a href="mailto:contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com" rel="nofollow ugc">contact-project+hmailserver-hmailserver-86730559-issue-@incoming.gitlab.com</a>, which GitLab files as one.</p>
]]></description><link>https://www.hmailserver.co.uk/topic/28263/active-directory-and-ldap-sign-in-directory-synchronisation-and-oauth2-bearer-tokens</link><generator>RSS for Node</generator><lastBuildDate>Fri, 06 Nov 2026 09:05:49 GMT</lastBuildDate><atom:link href="https://www.hmailserver.co.uk/topic/28263.rss" rel="self" type="application/rss+xml"/><pubDate>Sun, 27 Sep 2026 19:45:13 GMT</pubDate><ttl>60</ttl></channel></rss>