Regression in 0.16.17: Authentik OIDC bearer tokens rejected by /jmap/session (0.16.16 works)

Issue Description

Upgrading only the Stalwart binary from 0.16.16 to 0.16.17 causes valid externally issued OIDC bearer tokens to be rejected by the JMAP session endpoint.

The deployment uses Authentik 2026.5.6 as an external OIDC directory, Bulwark as the JMAP webmail client, and Caddy as the reverse proxy. The Stalwart configuration, database, OIDC provider, reverse-proxy configuration, and Bulwark configuration remained unchanged during testing.

This was reproduced as a controlled version comparison:

  • Stalwart 0.16.13: SSO works
  • Stalwart 0.16.16: SSO works
  • Stalwart 0.16.17: SSO fails
  • Rolling back from 0.16.17 to 0.16.16 immediately restores SSO

The failure occurred with both Bulwark 1.7.8 and 1.8.1. It was also reproduced from multiple independent clients, including a phone and a work laptop that had never previously accessed this webmail deployment. This rules out stale browser cookies or cached application state as the likely cause.

Expected Behavior

After Authentik successfully completes the authorization-code and token exchange, Stalwart should validate the resulting bearer token through the configured external OIDC directory and return an authenticated JMAP session.

This behavior should remain unchanged when upgrading from Stalwart 0.16.16 to 0.16.17 with the same configuration and externally issued token.

Actual Behavior

With Stalwart 0.16.17, Authentik authentication and token issuance complete successfully, but Stalwart returns HTTP 401 when Bulwark requests /jmap/session using the issued bearer token.

Caddy confirms that the request reaches the Stalwart upstream. An unauthenticated request to /jmap/session returns the expected anonymous JMAP session with HTTP 200, confirming that the endpoint and reverse-proxy route are operational.

Stalwart emits no warning-level journal entry explaining the rejected token.

Replacing only the 0.16.17 binary with 0.16.16 and restarting the service immediately restores authentication. Installing 0.16.17 again reproduces the failure.

Reproduction Steps

  1. Configure Stalwart to use an Authentik external OIDC directory for authentication.
  2. Run Stalwart 0.16.16 with Bulwark 1.8.1.
  3. Authenticate to Bulwark through Authentik.
  4. Confirm that Stalwart accepts the bearer token and returns an authenticated JMAP session.
  5. Replace only the Stalwart binary with version 0.16.17.
  6. Restart the Stalwart systemd service.
  7. Repeat authentication from a private browser window or a previously unused client.
  8. Observe that Authentik completes authentication and token issuance successfully.
  9. Observe that the subsequent /jmap/session request returns HTTP 401.
  10. Restore the 0.16.16 binary and restart Stalwart.
  11. Repeat the same authentication flow and observe that it works immediately.

The same 0.16.17 failure was also reproduced with Bulwark 1.7.8.

Relevant Log Output

Caddy access-log results for /jmap/session during a failed Stalwart 0.16.17 authentication attempt:

{"timestamp":1786477446.9052193,"status":204,"authorization":null}
{"timestamp":1786477447.420156,"status":401,"authorization":[]}
{"timestamp":1786477448.154787,"status":401,"authorization":[]}

The first entry is the unauthenticated preflight request. The following requests contain a redacted Authorization header and are rejected with HTTP 401.

Authentik recorded successful token requests from the Bulwark Node client during the authentication flow:

POST /application/o/token/ -> 200
auth_via=oauth_client_secret
user_agent=node

Direct unauthenticated JMAP session test:

GET /jmap/session -> 200
username=""
accounts={}

Stalwart warning-level journal output immediately after a failed attempt:

-- No entries --

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

OIDC

Additional Context

Environment:

  • Operating system: Ubuntu Linux x86_64
  • Installation: Native Linux binary managed by systemd
  • Affected Stalwart version: 0.16.17
  • Last verified working version: 0.16.16
  • Also verified working: 0.16.13
  • OIDC provider: Authentik 2026.5.6
  • JMAP client: Bulwark 1.7.8 and 1.8.1
  • Reverse proxy: Caddy

OIDC discovery succeeds from inside the Bulwark container. The discovered issuer, authorization endpoint, and token endpoint are reachable, and the token endpoint returns HTTP 200 during the real login flow.

A source comparison suggests a possible regression boundary in the dependency update that changed jsonwebtoken from 10.x using rust_crypto to 11.0.0 using aws_lc_rs:

The relevant external OIDC validation source appears otherwise substantially unchanged between 0.16.16 and 0.16.17. This dependency change is only a suspected regression boundary, not a confirmed root cause.

I cannot leave 0.16.17 running on the production mail server because it disables SSO. I retained the affected binary and can temporarily reproduce the issue with specific trace settings if the maintainers identify the relevant logging targets.

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

This isn’t covered in the current docs. A community member or staff member will follow up.

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

Please set the log level to trace, try authenticating and paste the logs here so we can see the full details. If you’re using RSA for the JWT access tokens, I believe aws-lc-rs has a minimum key size of 2048. That could be one reason, if not include what’s the key signing algorithm your IdP is using.

Update after retesting with trace logging:

I reinstalled Stalwart 0.16.17 and confirmed that the running process matches the installed binary:

  • Version: 0.16.17
  • SHA-256: 5d1258f9c387354525f6198be6f76d007bf40c1d0af9449b5b7242b73ae65e17

The original failure did not recur. Authentik SSO now succeeds from multiple clients, including a private browser session, and JMAP operations complete normally.

The Authentik signing configuration is:

  • Algorithm: RS256
  • Key type: RSA
  • RSA modulus: 4096 bits
  • JWKS contains one signing key

Trace logging produced 34 Authentication warning events. The structured journal output shows that every warning had the same cause:

MESSAGE="Authentication warning"
DETAILS="Directory account is not an email, appended default domain"
ACCOUNTNAME="<OIDC group name>"

There were 17 occurrences for each of two directory group principals. These warnings appear to be Stalwart normalizing non-email group names by appending the default domain; authentication continued successfully afterward. I have redacted the internal group names because their exact values do not appear relevant.

For completeness, the original incident was reproducible across multiple clients, including a device that had never previously accessed the webmail instance. At that time, requests carrying the OIDC bearer token were rejected by /jmap/session under 0.16.17. Replacing only the Stalwart binary with 0.16.16 immediately restored authentication. Reinstalling 0.16.17 later resulted in successful authentication without any configuration changes.

I do not have trace output from the original failure. I do, however, have trace captures from each successful authentication test performed after reinstalling 0.16.17 and will gladly provide them if they would help with analysis. I am currently unable to attach files to this case, possibly because of a new-user restriction, but I can paste relevant excerpts inline if requested.

I will keep 0.16.17 installed and capture trace-level output if the failure recurs.

0.16.17 bumped jsonwebtoken from 10.3.0 to 11.0.0 and switched it to the aws-lc-rs backend. But RSA 4096 with RS256 is well inside what aws-lc-rs accepts, so the minimum-key-size idea I raised is ruled out by your answer. The only behavioural change to external token validation in that release is that we now skip JWKS entries whose key type we don’t recognise, and an Authentik RSA key isn’t one of those. Issuer, audience and leeway handling are unchanged between the two builds.

The shape of what you saw fits something else: every bearer request rejected, the anonymous session endpoint still fine, nothing at warning level, and a plain reinstall clearing it. That’s what a failed OIDC directory startup looks like. We fetch the discovery document once at boot and the issuer we get back has to match the configured issuer URL exactly; if the fetch times out or the strings differ, the directory never loads and every token is rejected from then on, with the only trace of it in the startup log.

The warnings you found about appending the default domain to group principals are unrelated and harmless. If it recurs, the artifact I need is the log from service start on the failing boot rather than from the login attempt, plus whether Authentik was reachable from that host at the time. Worth comparing your configured issuer URL against the issuer value in Authentik’s discovery document character for character too; we normalise a trailing slash and nothing else.