Issue Description
I run an external OpenID Connect directory (type = “oidc”) whose issuerUrl points at a specific Kanidm OAuth client (https://auth.example.org/oauth2/openid/mail). Mail-protocol authentication (webmail via JMAP, and IMAP/SMTP OAUTHBEARER) works perfectly against this directory.
I expect the web interface (/account) SSO login to work against the same external IdP: after the user authenticates and Stalwart exchanges the authorization code, the returned token should be validated and an authenticated web session should be established.
Relevant docs: OpenID Connect backend.
Expected Behavior
The webadmin OIDC login completes: Stalwart validates the token returned from the IdP and establishes an authenticated /account session for the user.
Actual Behavior
The login flow reaches the IdP and the code exchange succeeds (the IdP returns HTTP 200 on /oauth2/token), but Stalwart fails to validate the returned token and falls back to HTTP Basic auth.
The root cause is a client/key mismatch: the webadmin redirects to the IdP with a hardcoded client_id=stalwart-webui, but Stalwart validates the returned token against the directory’s JWKS (the issuerUrl configured for the OIDC directory, e.g. …/openid/mail). With IdPs that sign each OAuth client’s tokens with a distinct key (Kanidm scopes issuer and JWKS per client: …/openid//public_key.jwk), the stalwart-webui token is signed by a key that is absent from the mail client’s JWKS → validation fails.
This works with IdPs that expose a single shared JWKS across all clients (Authentik, Keycloak), but not with per-client-JWKS IdPs like Kanidm. There is no setting to change the webadmin client_id to the one the directory uses, nor to validate the webui token against its own iss/JWKS.
Request: make the webadmin OIDC client_id configurable (so it can reuse the directory’s client), or validate the webui token against the token’s own iss/JWKS (from OIDC discovery of the token issuer) rather than the directory’s issuerUrl
Reproduction Steps
-
Configure an external OIDC directory and set it as the default (
Authentication.directoryId+ the default domain’sdirectoryId):directory."idp".type = "oidc" directory."idp".issuer-url = "https://auth.example.org/oauth2/openid/mail" # a specific IdP client "mail" directory."idp".require-audience = "mail" directory."idp".claim-username = "email" -
On the IdP (Kanidm), register the webadmin client Stalwart expects: a public/PKCE client named
stalwart-webuiwith redirect URIhttps://mail.example.org/account/oauth/callback. (Kanidm signs this client’s tokens with a key distinct from themailclient’s key.) -
Open
https://mail.example.org/account, choose SSO login, authenticate at the IdP and consent. -
Observe: the browser returns to
/account/oauth/callback, then Stalwart challenges HTTP Basic auth instead of establishing a session.
Stalwart Version
v0.16.x
Installation Method
Built from source
Database Backend
PostgreSQL
Blob Storage
S3-compatible
Search Engine
Internal
Directory Backend
Internal
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
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