Your question
We run Stalwart Enterprise (v0.16, clustered), directoryId → an LDAP directory (Authentik’s LDAP outpost) that handles both authentication and offline RCPT resolution. Inbound mail must resolve for any provisioned mailbox whether or not the user has ever logged in.
We’d like our JMAP webmail to authenticate users with Authentik-issued OIDC tokens (OAUTHBEARER) so Authentik’s login flow + MFA actually run. As we read it, the Authentication singleton takes a single directoryId used for both auth and recipient validation, and an OIDC directory can’t do offline lookup, so pointing it at OIDC would reject inbound mail for not-yet-authenticated mailboxes and break app-password (basic-auth) IMAP/SMTP.
-
Is there a supported way to validate OIDC bearer tokens for authentication while keeping LDAP or the internal directory for recipient resolution and basic-auth (separate auth vs. lookup directory, a fallback/composite directory, or per-mechanism selection)?
-
If not, is the recommended topology to make the internal directory the principal source (we provision all mailboxes/aliases via JMAP) and layer OIDC auth on top? Would offline RCPT then resolve from the internal directory for an account that has never authenticated?
Happy to share our current Authentication and x:Directory configuration if useful.
Thanks for building Stalwart, and for any guidance on the right topology here!
This confirms the architecture: directoryId is a single value on the Authentication singleton, it’s the only slot for an external directory, and there’s no documented composite/fallback directory mechanism or per-mechanism directory selection. I have enough to answer confidently now.
Answer
There’s no supported “auth here, lookup there” split for the Authentication singleton. directoryId is a single field that selects one external directory (or none, for the internal directory) used for both authentication and account/recipient resolution — there’s no fallback or composite directory concept in the docs, and no per-mechanism directory override.
For OIDC specifically, the docs state this limitation explicitly: OIDC provides no offline directory lookup, so an account known to the IdP but that has never signed in is unresolvable, and mail to it is rejected. The documented mitigation is exactly your option 2: pre-create accounts (via WebUI, JMAP, or CLI) before first login, so recipient resolution always succeeds regardless of authentication history.
So the supported topology is:
- Set
directoryId on Authentication to your OIDC Directory object for OAUTHBEARER logins.
- Provision all mailboxes/aliases as internal accounts ahead of time (this is what you’re already doing via JMAP), since with an OIDC directory selected, the internal directory is not consulted for lookups — you must ensure every mailbox exists in Stalwart itself before OIDC sign-in, matching the OIDC claim-derived username/email.
- For basic-auth IMAP/SMTP and app-passwords, note the docs’ interoperability guidance: many clients (Outlook, Thunderbird, Apple Mail) can’t do OAUTHBEARER against third-party IdPs at all, so app passwords are the standard workaround there, generated against the pre-provisioned internal account.
There’s no documented mechanism to point Stalwart at LDAP for lookups while using OIDC purely for auth-token validation in parallel. Since you’re already treating the internal directory as the mailbox source of truth via JMAP, that’s the path the docs endorse — you likely don’t need LDAP delegation for authentication/lookup at all if the internal store is authoritative; LDAP would only be needed if you want Stalwart to defer account existence to Authentik’s LDAP outpost, which conflicts with the offline-lookup requirement per the same limitation.
Docs referenced: OIDC backend, Directory backend overview, Internal directory, Authentication object reference.
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.
Thanks, that confirms there’s no auth/lookup split, which matches our own testing (an Authentik-signed bearer only decodes against whichever single directory is set as Authentication.directoryId).
Two follow-ups on the recommended path (directoryId → OIDC + pre-create every account as an internal principal via JMAP), which are the parts that would make or break it for us. Could a maintainer confirm:
- Offline recipient resolution, with directoryId pointing at the OIDC directory, will inbound mail to a pre-created internal mailbox resolve before that user has ever authenticated? OIDC has no offline lookup, so I want to be sure the internal store is still consulted for RCPT when OIDC is the active auth directory (otherwise mail to not-yet-logged-in mailboxes would be rejected).
- Basic-auth / app-passwords, with OIDC as the active directory, what validates app-password binds for IMAP/SMTP clients that can’t do OAUTHBEARER? Does the internal store still authenticate those in parallel, or does making OIDC the active directory disable password auth entirely?
Essentially: is “internal directory as the account/recipient source of truth, OIDC layered on top purely for token auth, app-passwords for legacy clients” a supported, working topology today? If so, a pointer to the exact Authentication / Directory config would be hugely helpful.
There’s no separate auth-vs-lookup directory; there’s one directoryId. But that doesn’t break offline resolution the way you’re worried about. An OIDC directory reports that it can’t resolve recipients, so recipient resolution skips it and consults the internal registry. That means inbound mail to a pre-created internal mailbox resolves before that user has ever authenticated. So your option 2 is the supported topology: make the internal directory the account and recipient source of truth (provision mailboxes/aliases via JMAP), and set directoryId to the OIDC directory for token auth. Answer to your Q1: yes, offline RCPT still works.
On app passwords (Q2): app-password binds are validated against the internal store before the active directory is consulted, so IMAP/SMTP clients using app passwords keep working alongside OIDC. The one caveat to be explicit about: a plain (non-app-password) basic-auth bind is routed to the active directory, and OIDC only accepts OAUTHBEARER, so ordinary internal-store passwords stop validating once OIDC is the active directory. Legacy clients therefore need to use app passwords rather than the account’s normal password. So “internal directory as source of truth, OIDC layered on for token auth, app passwords for legacy clients” is supported and works today. See: OpenID Connect | Stalwart.
Thanks, confirming this topology works as you describe. I tested it on a fresh v0.16.12 instance (internal directory as account/recipient source of truth, directoryId pointed at an OIDC directory):
- Inbound RCPT to pre-created internal principals resolves with the OIDC directory active, including accounts that have never authenticated, and their aliases. Unknown localparts still get 550.
- App-password IMAP binds keep working, validated against the internal store as you said.
- Plain-password binds are rejected (“Unsupported credentials type for OIDC backend”), as expected.
- Bearer auth against JMAP works end-to-end; validation is offline via JWKS (one discovery fetch at startup, one JWKS fetch on first token, nothing per-login — nice).
Two observations from testing, then a request.
Observations / questions:
- A validly-signed token whose username claim matches no existing principal auto-creates one, and in a multi-tenant deployment the created account has no tenant. Is there a way to disable auto-provisioning on
an OIDC directory, or scope it (via memberTenantId)? We provision all principals through the management API and would prefer token auth to fail closed for unknown subjects.
- If the discovery fetch fails at server startup, the directory is dropped (registry.build-error) and the server starts with a dangling directoryId — bearer auth then 401s while internal passwords validate
again. Mail keeps flowing, which is good, but the auth semantics change silently on a security boundary. Would a hard-fail or retry option at startup make sense?
The request: the one thing keeping us from adopting this topology in production is that plain-password binds stop validating entirely. Our users have a single IdP password, and requiring app passwords for every IMAP/SMTP/DAV client on every device is significant friction. Would you consider an optional fallback directory for password binds, directoryId → OIDC for bearer tokens, with basic-auth credentials dispatched to a secondary directory (LDAP or internal)? Credential-type dispatch already effectively exists (app passwords and API keys are checked against the internal store before the active directory is consulted), so conceptually this extends that to plain binds. If a PR would be welcome we’d be glad to contribute, or point me at wherever you’d prefer a formal feature request to live.
Context: we run Stalwart (Enterprise license) as the mail pillar of a sovereign EU business workspace (email.eu), with Authentik as the IdP.
Thanks for the feedback. Please add your feature enhancement requests under the “Feature Ideas” topic.