Issue Description
Upfront: I’m unsure if this counts as a feature request or bug report. I filed it here because it involves a protocol (RFC 8707) that is implemented in stalwart’s internal IdP, but is not extended to external IdPs, which results in unexpected auth failures under intuitive configurations.
tl;dr: when trying to use multiple OAuth registrations, at an external OIDC, with requireAudience set, the OIDC directory can mint a valid token for the WebUI, but Stalwart rejects it because the token does not contain the required API audience.
Stalwart’s external OIDC directory supports requireAudience and requireScopes, which reject access tokens unless their aud claim and required scopes match the configured values. The Stalwart WebUI is an OAuth client of the same identity provider, and Stalwart validates its access tokens using that external OIDC directory. However, the WebUI has no runtime way to request the audience/resource required by Stalwart, nor any way to discover which value the server requires.
The problem occurs when:
- Accounts are authenticated via an external OIDC directory.
- requireAudience and/or requireScopes is configured on that directory.
- Those accounts sign in to the Stalwart WebUI.
- The WebUI’s token either has an audience different from requireAudience or lacks one or more required scopes. This occurs when you try to use different OAuth client registrations for the WebUI and jmap client.
In that case, WebUI login fails, while JMAP and other clients that request the correct audience and scopes continue to function.
For example, when no resource parameter is included in the authorization request, PocketID uses the OAuth client ID as the default aud value. If the WebUI’s client ID is stalwart-webui and Stalwart is configured with requireAudience=https://jmap.example.com, then PocketID will mint a token for the webUI with aud=stalwart-webui which will be rejected by stalwart.
A straightforward fix is to allow the WebUI to discover and request the resource and scopes that Stalwart already requires. The server already has this information in the external OIDC directory configuration. So its existing discovery endpoint could add optional fields resource (RFC 8707) and required scopes. The WebUI could then consume those fields and the token minted by the IdP would be accepted by stalwart.
Stalwart already implements RFC 8707, but only for its internal authorization server. The extension to the endpoint would allow deployments to use separate OAuth client registrations for the WebUI, webmail, mobile clients, and other applications while still issuing tokens for the same Stalwart API. It would also preserve the existing behavior when no audience or scopes are configured: the new fields would simply be omitted from discovery, and existing clients would be unaffected.
Expected Behavior
When an external OIDC directory is configured with requireAudience and/or requireScopes, the Stalwart WebUI should be able to complete login using that directory.
The WebUI should discover the required resource and scopes from Stalwart, include them in its OAuth authorization and token requests, and receive an access token that satisfies the directory’s configured requirements. Stalwart should then accept the token and complete the login.
Actual Behavior
When the WebUI and JMAP use separate OAuth client registrations, the WebUI cannot request the API resource required by Stalwart. The identity provider therefore issues a token whose audience does not match requireAudience, and WebUI login fails. There is currently no runtime WebUI configuration or discovery response that provides the required resource.
Reproduction Steps
Configure an external OIDC provider with:
- An API/resource identifier, for example
https://mail.example.com. - A permission/scope, for example
mail.read. - Two separate OAuth client registrations:
- one for the Stalwart WebUI
- one for a JMAP or other API client.
Grant both clients access to the API resource and scope.
Configure Stalwart’s external OIDC directory with:
requireAudience=https://mail.example.com
requireScopes=mail.read
Configure the non-WebUI client to request:
resource=https://mail.example.com
scope=openid mail.read
Verify that it receives an access token whose aud contains https://mail.example.com and whose scopes include mail.read. Requests using that token should succeed.
Sign in to the Stalwart WebUI.
Inspect the WebUI’s authorization request. It does not include the configured API resource, because the WebUI has no runtime setting or discovery field from which to obtain it.
The identity provider issues an access token using its default behavior. I use PocketID, so the token audience is the WebUI’s client ID rather than the API resource.
Stalwart rejects the token because its audience does not match requireAudience, and WebUI login fails
Stalwart Version
v0.16.x
Installation Method
Binary (Linux)
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
Additional Context
I have forks of stalwart and the webui at GitHub - Sorontooo/stalwart: All-in-one Mail & Collaboration server. Secure, scalable and fluent in every protocol (IMAP, JMAP, SMTP, CalDAV, CardDAV, WebDAV). · GitHub and GitHub - Sorontooo/webui: Stalwart WebUI · GitHub that implement the fixes. I disclose that LLMs helped me diagnose the issue and implement the fixes.
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