Web UI sends "[email protected]%[email protected]" as username to OIDC provider when trying to impersonate user

Issue Description

When using OIDC directory, it is not possible to impersonate users in Web UI with <target>%<impersonator> request (<target> and <impersonator> being emails addresses of user and admin with impersonate permission respectively), as specified in documentation (Administrators | Stalwart).

Expected Behavior

Web UI should first parse the “username” for impersonation request, and only send impersonator’s username/email to OIDC provider.

Actual Behavior

Full request, [email protected]%[email protected], is sent to OIDC provider as username; any login attempt fails since such user doesn’t exist.

Reproduction Steps

  1. Configure Stalwart to use OIDC provider.
  2. Give administrator role or specifically impersonate permission to user A ([email protected]).
  3. Attempt to impersonate user B ([email protected]) by entering [email protected]%[email protected] as username in Web UI sign-in page.

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

PostgreSQL

Blob Storage

Filesystem

Search Engine

Elasticsearch

Directory Backend

OIDC

Additional Context

We use Keycloak 26.6.2 Docker container as our OIDC provider.

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

Let me look into the impersonation flow with OIDC to understand how this is handled in the codebase.

I’ve found the key part in the server code. The server-side logic in authentication.rs correctly parses the impersonation composite username and, when an external directory is in use, sends only auth_as_address (the impersonator/master user’s address) to the external directory. The logic is on line ~175:

let directory_account = if username.is_master() {
    directory
        .authenticate(&Credentials::Basic {
            username: auth_as_address.to_string(),  // only the impersonator part
            ...
        })

So the server-side handling looks correct. The problem, as you’ve identified, is that the Web UI is sending the full composite string [email protected]%[email protected] directly to the OIDC provider’s login endpoint, rather than routing the authentication through the Stalwart server’s own auth layer first.

Impersonation does not work when using external OIDCs, you’ll have to use the recovery admin in those cases.

How can I use recovery admin to create an App Password for target user?