Your question
Hello,
Apologies if this seems like a trivial ask. We are trying to get OIDC working with Stalwart, and conversely Bulwark. We have multiple domains, and noticed that this particular feature is greyed out as an enterprise feature under a domain.
My question is, is this feature required? When trying to login to Stalwart it seems like even if we assign an OIDC directory under Settings > Authentication > General it is still logging in with local credentials. We are using our own OIDC provider. Is there any step-by-step instructions on setting this up? We have multiple domains, do we need to set up OIDC for each, or can we just set up OIDC as the main authentication for all?
Thank you.
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on
I now have enough to answer clearly.
Per-domain OIDC directory assignment is an Enterprise feature; the default single OIDC directory (Settings › Authentication › General) is not. In the community edition, you set one directory (directoryId) as the server-wide authentication backend, which applies to all domains. Assigning a different directory per individual domain (the directoryId field on the Domain object, greyed out for you) requires an Enterprise license — this lets each domain use its own LDAP/OIDC/SQL backend independently.
To use OIDC for all of your domains at once (no Enterprise license needed):
- Create an OIDC-type Directory object under Settings › Authentication › Directories, with your provider’s
issuerUrl, claimUsername, usernameDomain, claimName, claimGroups, etc. (See the config example below.)
- Go to Settings › Authentication › General and set
directoryId on the Authentication singleton to that Directory’s id. This becomes the default backend used to authenticate logins across your entire server, regardless of which of your domains the user belongs to.
- Important limitations to be aware of:
- Stalwart only learns about an OIDC account the first time it authenticates — pre-create accounts (WebUI, JMAP, or CLI) if you need mail delivered before first login.
- Many mail clients (Outlook, Thunderbird, Apple Mail) don’t support
OAUTHBEARER/XOAUTH2 with third-party providers, so those users will still need App Passwords for IMAP/SMTP even after OIDC is set up — WebUI/JMAP logins go through OIDC directly.
- Setting the directory here does not disable local/internal credentials on other directories; it becomes the authentication source resolved for domains that don’t have their own explicit directory (i.e., all of them, in your case, since per-domain assignment is Enterprise-only).
Example OIDC directory config:
{
"@type": "Oidc",
"description": "External IdP",
"issuerUrl": "https://accounts.example.org/realms/myrealm",
"requireAudience": "stalwart",
"requireScopes": ["openid", "email"],
"claimUsername": "preferred_username",
"usernameDomain": "example.org",
"claimName": "name",
"claimGroups": "groups"
}
If logins are still falling back to local credentials after setting directoryId, double check that the Authentication singleton actually saved the change and that Bulwark/your client is presenting the OIDC access token via OAUTHBEARER rather than a password — password-based logins against an OIDC-backed directory won’t validate against your IdP the same way.
Reference: OpenID Connect | Stalwart and Domain | Stalwart
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.
The per-domain OIDC provider assignment is an Enterprise feature, but you don’t need it for basic OIDC: a single OIDC directory set as the global directory under Settings > Authentication > General serves all your domains, so there’s no need to assign it per-domain.
On login still using local credentials: that’s expected for how OIDC works here. Stalwart’s OIDC directory validates tokens that clients present (OAUTHBEARER); it doesn’t turn the WebUI password form into a redirect to your IdP. If you set the OIDC directory as the sole active directory, plain-password logins are actually rejected rather than silently accepted, so the fact that local login still works suggests the OIDC directory isn’t set as the active directory yet (or you’re logging in as the internal recovery admin). Can you say what you mean by OIDC login here: a redirect-to-IdP SSO for the admin panel (which isn’t native), or token-based auth for mail clients? And is the Authentication directory actually pointed at the OIDC directory?
So, if the default WebUI login form won’t redirect to the external directory, how are we supposed to log into the WebUI when only an external OIDC directory is configured? Is there any example of this kind of setup?
Triggering a login flow to my OAuth2 server that redirects to the callback (https:///account/oauth/callback) leads to a 400 error either way
Update: now I AM getting redirected to my IDP’s login page, then the consent page, and then back to Stalwart who 401s because the token’s missing the audience, and if I force the login link to request the audience, it’s now missing scopes
For anyone here: the redirection just started working. I did test by clicking the “reload server” or something like that action, I guess that did it.
So my question is: are the requested scopes hardcoded? Because the server seems to require the scopes written in the directory config, but when the login page redirects you there, it only requests oidc and offline_access, so the consent page only asks for these and my OAuth2 server understandingly only gives the requested scopes
@Palomox : the 401s are the OIDC directory’s requireAudience and requireScopes validations firing. The WebUI’s own login redirect requests openid and offline_access, so the token’s aud won’t match a requireAudience you’ve set to something else, and the scopes it carries won’t include extras you’ve required. The fix is to align the directory config with what your IdP actually issues and grants: set requireAudience to the audience value present in the tokens (the client_id/resource your IdP mints) and requireScopes to a subset the IdP actually returns, rather than forcing the client to request more than the consent page offers. If you can tell me which client is initiating the flow and paste the aud and scope claims from a token it presents (redacted), I can tell you the exact values to set. Note that interactive redirect SSO into the WebUI isn’t the native path here; the directory is primarily for validating tokens that clients present.