I cannot get third party OIDC provider to work

Your question

I’m trying to get Rauthy set up with Stalwart, so that I can use that as the Directory.

After stalwart obtains a JWT from rauthy and can proceed, for some reason the /jmap/session endpoint returns 401 Not Authorized, even though the request contains a JWT.

I found that the response contains Bearer realm="Stalwart Server", resource_metadata="/.well-known/oauth-protected-resource", so I followed that resource metadata link, and found that authorization_servers":["https://mail.hackcraft.dev"] (my stalwart instance), but not auth.hackcraft.dev.

I think this might be the problem and finding a way of changing authorization_servers might be what I need to do.

Here are my questions:

  1. Is the authorization_servers the right lead?
  2. How do I affect authorization_servers?

Thank you.

The authorization_servers field in /.well-known/oauth-protected-resource isn’t something you configure and it isn’t the reason your JMAP request is rejected. In the current code that endpoint always reports Stalwart’s own base URL as the authorization server, because it describes Stalwart’s built‑in OAuth server (used for RFC 9728 resource-metadata discovery, e.g. by MCP-style clients), not your third-party OIDC provider. It’s unrelated to how Stalwart validates bearer tokens issued by an external IdP like Rauthy.

The actual OIDC-directory authentication path works differently: when a bearer token arrives, Stalwart decodes the JWT, looks at its claims, and resolves which Directory to validate it against. For a token with no recognizable domain claim, it looks up the directory by matching the token’s iss claim against the issuerUrl/discovery document of your OIDC Directory objects — but that issuer-matching lookup (get_directory_for_issuer) is only compiled in for the Enterprise edition. In the non-Enterprise build, Stalwart falls back to whatever directory is set as the default via directoryId on the Authentication singleton. So the things to check are:

  1. Is your Rauthy-backed OIDC Directory object actually set as the default directory (directoryId on the Authentication singleton)? If not, and you’re on the free/non-Enterprise build, per-issuer routing won’t kick in and the token will fail.
  2. Does the token’s aud claim match requireAudience on your Directory object (defaults to "stalwart")?
  3. Does issuerUrl on the Directory exactly match what’s in the Rauthy-issued token’s iss and does Stalwart succeed in fetching its discovery document (check server logs for OIDC/JWT validation errors)?

See the configuration section of the OIDC backend docs: OpenID Connect | Stalwart

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

authorization_servers isn’t it. That field describes Stalwart’s own built-in OAuth server, it’s fixed, and it plays no part when Stalwart checks a token issued by Rauthy.

My first suspect is the audience. The OIDC directory requires the token’s aud to contain requireAudience, which defaults to stalwart, while Rauthy puts the client id there. Decode one of the tokens and compare; if that’s it, set requireAudience to the Rauthy client id. The same check wants openid and email in the token’s scopes (requireScopes) and an iss that matches the issuer exactly. Directory settings only apply after Reload server settings. OpenID Connect | Stalwart has all three.

If the audience turns out fine, the auth.failed or auth.error event logged with the 401 names the failed check, something like InvalidAudience or a missing scope. Post that line and I’ll take it from there.

Thank you for your help. I did indeed change aud to the client_id, without results. For some reason, without doing anything, it started working a while. I suspect there is a delay even after reloading.

Thank you for your assistance.

I found the underlying problem:

In the directory config, there is this paragraph:

The claim name used to retrieve the user's login name from the token or user info response. Common values are preferred_username, email, or sub depending on your provider's configuration. If the claim value is not an email address and usernameDomain is set, the domain will be appended automatically (e.g. john becomes [email protected]). If the claim value already contains an @, it is used as-is. If the claim value is not an email address and no usernameDomain is configured, Stalwart will fall back to the email claim. If neither yields a valid email address, authentication will be rejected.

I believe this to be untrue.

In cases where it failed, I used an external email address with the OIDC provider. Even though the Username Claim is set to preferred_username and Username Domain is set appropriately to my domain, it still uses the email claim to generate the new address.

I have tested the following scenario:

  1. create an account in Rauthy with a gmail mail (e.g. [email protected])
  2. set preferred_username to “ABC”
  3. go to stalwart; notice that authentication fails
  4. change the email to [email protected]
  5. go to stalwart; notice that the new email address is [email protected] instead of [email protected]

I believe the behaviour stated in that paragraph should be implemented, and the current observed behaviour is a bug.

Fixed in v0.16.26 (yet to be released).