Confused about OIDC login configuration for Stalwart WebUI

Your question

Hi,
I have Stalwart configured with an OIDC directory pointing to my Authentik instance for Bulwark webmail (client_id = bulwark-mail-client). Everything works fine for Bulwark.

Now I want to set up SSO login for the Stalwart admin panel (/admin) via the same Authentik instance. I created a separate OAuth2 provider in Authentik and a new OAuthClient object in Stalwart with clientId = "stalwart-admin-panel".

The problem: when I try to log in to the admin panel, Stalwart redirects to Authentik with client_id=stalwart-webui, but I never created an OAuthClient with that ID. It completely ignores my custom OAuthClient object.

Questions:

  1. Where does client_id=stalwart-webui come from? Is there a built-in client that I’m not aware of?
  2. How is the OAuthClient object supposed to be used for WebUI login — does Stalwart actually check it, or is it purely for third-party clients?
  3. What is the correct way to configure OIDC-based login for the admin panel when using an external OIDC provider (Authentik) as the directory backend?
  4. Or am I approaching this wrong entirely — should the Stalwart WebUI and Bulwark share a single OIDC directory, and the separation between them should be handled on the Authentik side?

Thanks

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

A few things are getting conflated. client_id=stalwart-webui is the admin UI’s own built-in client id, baked into the SPA; you don’t create it, and it ignores any OAuthClient you define because client registration isn’t required by default. OAuthClient objects are for third-party/registered clients and only get enforced when you turn “require client registration” on, so your stalwart-admin-panel client isn’t being used for admin login.

To get Authentik-backed admin login, point Stalwart’s authentication source at your Authentik OIDC directory so admin credentials resolve through Authentik (make sure to grant admin role in Stalwart to that user), rather than creating a separate Stalwart OAuthClient; the same OIDC directory can back both mail-client and admin logins, and per-app separation belongs on the Authentik side. Relevant docs: OpenID Connect | Stalwart (the OIDC directory backend) and OAuth Clients | Stalwart (what OAuthClient objects are for).

how can i disable or configure default client?

The stalwart-webui client id is baked into the admin SPA itself; it’s the id the WebUI uses to talk to Stalwart’s own OAuth server, and it isn’t the connection to Authentik. There’s no runtime setting to disable it, and you don’t need one: by default Stalwart doesn’t require client registration (requireClientRegistration is false on the OidcProvider singleton), so any client id is accepted and the server issues tokens without consulting the OAuthClient registry. That’s why your stalwart-admin-panel object is ignored.

If you do want registration enforced, set requireClientRegistration to true on the OidcProvider singleton, and then create an OAuthClient with clientId stalwart-webui (with the right redirect URIs) so the WebUI itself keeps working, otherwise it’ll stop getting tokens once registration is required. Changing the id itself would mean rebuilding the WebUI with VITE_OAUTH_CLIENT_ID, which I wouldn’t bother with.

Separately, the Authentik-backed admin login is the OIDC directory doing the authentication (as covered above): the same directory can back both Bulwark and admin logins, and the per-app split belongs on the Authentik side.

Hi @jaam8 – Did you get this working? I’m evaluating Stalwart and Bulwark as well, and running into this problem. Using the same OIDC provider results in invalid client_secret errors, and can no longer log in to the admin UI. Creating a separate provider and setting the provider as Public also did not work for me.

If you have it working, do you mind sharing both your Authentik config for your provider(s), and Stalwart config? Since I’m using RocksDB, I do not have config.toml and it appears v0.16 removed the fallback account.

I just wanted to update this thread. What we ended up doing was moving from OIDC to strictly LDAP, since Authentik supports LDAP. In Bulwark, used the “Autoconfigure OIDC (Stalwart)” feature, removed any instance of OIDC from the docker environments and disabled OAuth / OpenID Connect section. When a user goes to Bulwark, they enter their username/password and that sends the authentication request to Stalwart, which in turn validates against Authentik.

For our uses, we’re developing a platform where all tenant, domain, and email provisioning is done via the JMAP API, so technically while we generally will not need to log in to the UI, we will need to be able to authenticate, and need the admin ui for occasional issues.

We did change the LDAP filters.

Mailbox Filter:

(&(objectClass=user)(memberOf=CN=stalwart-mailboxes,OU=groups,DC=ldap,DC=goauthentik,DC=io)(mail=?)(ak-active=TRUE)) - We are adding a group called “stalwart-mailboxes” to all users who will have access to email, so we filter to ensure the user is a member of that group.

Still interested if you were able to get OIDC working end to end. If you did, might be worth creating an Integration guide for Authentik, I’m considering creating one for LDAP.

To recap for anyone landing here: client_id=stalwart-webui is the admin SPA’s own built-in client for talking to Stalwart’s OAuth server. It’s not the connection to Authentik, there’s no runtime toggle for it, and you don’t create an OAuthClient for it unless you turn on requireClientRegistration. Authentik-backed login is done by configuring Authentik as an OIDC directory (the consumer side) and setting it as the authentication directory; the same directory can back both Bulwark and admin logins, with the per-app split handled on the Authentik side.

@ObsidianGroup, on the lockout: the fallback admin was not removed in 0.16, it’s now called recovery admin.

@stalwart Recovery Admin requires restarting the container in recovery mode, but is not the same as a fallback admin. With recovery admin, at least my understanding, it’s to be used if there’s a configuration issue and you are unable to login, and you should not run in that mode at all times. A fallback admin would not require the process to be restarted, and running in this mode.

The issue I was having with OIDC, I did not see that Bulwark itself supported PCKE. I may try OIDC completely again, just testing to see if with changing the OIDC from Confidential to Public, and not entering an OIDC_SECRET for Bulwark, if that works.

On the PKCE question: yes, a public client with PKCE (S256) and no client secret is the right shape for a browser SPA like Bulwark, and Stalwart’s OAuth supports S256, so that’s worth trying before concluding it can’t work end to end.

Hello guys, i managed to get OIDC authentication working with Stalwart version 0.16.8 and Authentik version 2026.8.0.

First of all, it is not necessary to register oidc client under Management -> OAuth Clients.

Only create an OpenID Connect provider under Settings -> Authentication -> Directories

The Directory have been configured with the following parameters:

Description: Authentik
Issuer URL: https://<fqdn of authentik>/application/o/stalwart/
Required Audience: stalwart-webui

One very important part is, that the OIDC application type in Authentik is set to Public and that the client id is set to stalwart-webui.

The following redirect uri’s need to be added to the oidc application in Authentik:

https://<fqdn of stalwart server>/account/oauth/callback
https://<fqdn of stalwart server>/admin/oauth/callback

In case you want Bulwark oidc authentication also working, you need to set the following environment variables:

JMAP_SERVER_URL=https://<fqdn of stalwart server>
OAUTH_ENABLED=true
OAUTH_ONLY=true
OAUTH_CLIENT_ID=stalwart-webui
OAUTH_ISSUER_URL=https://<fqdn of authentik>/application/o/stalwart

The following 2x redirect uris need to be added to the oidc application in authentik, to get the callback working.

https://<fqdn of bulwark server>/en/auth/callback
https://<fqdn of bulwark server>/de/auth/callback

At the Stalwart server it is also important to enable the Permissive CORS policy under Settings -> Network -> HTTP -> Security. This setting is only needed when you run the Stalwart and Bulwark server on two different urls.

I have to mention that i run the entire setup with a docker compose setup. The admin interface (Stalwart server) and the webmail interface (Bulwark) are running in my case on two different domains.