Stalwart : connexion au WebUI via Authelia (OIDC)
Connexion du WebUI de Stalwart (v0.16, édition communautaire) via Authelia en OIDC : la configuration qui fonctionne, la limite qui m'a fait revenir en arrière, et les alternatives.
En bref
J’ai réussi à me connecter au WebUI de Stalwart avec Authelia (SSO + double facteur). Mais dans l’édition communautaire, l’annuaire OIDC est global : une fois activé, il remplace l’authentification de tous les comptes. Résultat : plus aucune connexion SMTP/IMAP par mot de passe. J’ai fait marche arrière, et je reste sur l’annuaire interne avec du 2FA pour les comptes administrateurs.
N’activez pas un annuaire OIDC sur un serveur de messagerie en production sans avoir lu la section « La limite » ci-dessous.
Les domaines ci-dessous sont des exemples : mail.example.org (Stalwart) et auth.example.org (Authelia).
Contexte
Depuis la v0.16, Stalwart stocke toute sa configuration dans la base, sous forme d’objets JMAP.
Le WebUI, stalwart-cli et les appels JMAP directs utilisent la même API : on peut donc tout paramétrer en ligne de commande.
Testé avec stalwart-cli 1.0.12.
Comment fonctionne la connexion OIDC du WebUI
- Dans le formulaire de connexion, on saisit l’adresse complète (
staladmin@example.org), sans mot de passe. - Le WebUI interroge
/api/discover/<adresse>: si l’annuaire est OIDC, le serveur répond avec les endpoints du fournisseur (Authelia). - Le navigateur est redirigé vers Authelia, puis revient sur
/admin/oauth/callbackavec un code. L’échange du code contre le jeton est un appel direct du navigateur vers Authelia : il faut donc le CORS. - Stalwart valide le jeton (signature, audience, scopes) et retrouve le compte correspondant.
Le compte doit déjà exister dans Stalwart, et son nom doit correspondre à l’identité fournie par Authelia.
Configuration qui a fonctionné
Authelia : CORS
1
2
3
4
5
6
7
8
9
10
11
12
identity_providers:
oidc:
cors:
endpoints:
- authorization
- token
- revocation
- introspection
- userinfo
allowed_origins:
- https://mail.example.org
allowed_origins_from_client_redirect_uris: false
Sans cela, le WebUI affiche Failed to fetch au retour d’Authelia.
La section cors est globale au fournisseur et n’affecte pas les autres clients (les applications côté serveur n’envoient pas d’en-tête Origin).
Authelia : client public avec PKCE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
clients:
- client_id: 'stalwart-webui'
client_name: 'Stalwart'
public: true
authorization_policy: 'two_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://mail.example.org/admin/oauth/callback'
- 'https://mail.example.org/account/oauth/callback'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'RS256'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'none'
- Client public : le WebUI est une application navigateur, il n’envoie jamais de secret.
- Les deux redirect URIs :
/admin/...et/account/.... RS256: avecnone, Authelia émet des jetons opaques, que Stalwart refuse par défaut.offline_accessetrefresh_token: d’après des retours sur le forum de Stalwart, le WebUI demande ce scope. Si Authelia répondinvalid_scope, c’est ici qu’il faut regarder.
Redémarrer Authelia après modification.
Stalwart : annuaire OIDC
Objet Directory de type OpenID Connect :
| Champ | Valeur |
|---|---|
| Issuer URL | https://auth.example.org (racine d’Authelia, sans slash final) |
| Required Audience | stalwart-webui (le client_id) |
| Required Scopes | email, openid |
| Username Claim | preferred_username |
| Username Domain | example.org |
| Name Claim / Groups Claim | name / groups |
L’issuer doit correspondre exactement à celui annoncé par Authelia.
Les noms de champs dépendent de la version : stalwart-cli describe <Objet> donne les noms exacts (par exemple requireAudience avec mon CLI, acceptedAudiences dans la documentation récente).
1
stalwart-cli update Directory <id-directory> --field requireAudience=stalwart-webui
Stalwart : client OAuth interne et client id du WebUI
1
2
3
4
5
6
7
stalwart-cli create OAuthClient \
--field clientId=stalwart-webui \
--field description='Stalwart WebUI' \
--field 'redirectUris={"https://mail.example.org/admin/oauth/callback":true,"https://mail.example.org/account/oauth/callback":true}'
stalwart-cli query Application
stalwart-cli update Application <id> --field oauthClientId=stalwart-webui
Créer l’OAuthClient avant de renseigner oauthClientId, sinon l’administrateur de récupération ne peut plus se connecter.
Activation
1
2
stalwart-cli snapshot # sauvegarde avant
stalwart-cli update Authentication --field directoryId=<id-directory>
Retour arrière :
1
stalwart-cli update Authentication --field directoryId=null
Vérification :
1
curl -s https://mail.example.org/api/discover/staladmin@example.org | jq
La réponse doit contenir l’issuer, authorization_endpoint, token_endpoint et jwks_uri d’Authelia.
La limite : l’annuaire OIDC est global
directoryId de l’objet Authentication désigne l’annuaire utilisé pour authentifier tout le serveur.
L’assignation d’un annuaire par domaine existe, mais c’est une fonction Enterprise (grisée dans l’édition communautaire).
Une fois l’annuaire OIDC actif :
- l’annuaire OIDC n’accepte que des jetons présentés par les clients, pas de mots de passe ;
- les mots de passe internes ne servent plus, ni pour le WebUI, ni pour SMTP/IMAP ;
- la plupart des clients mail ne savent pas utiliser OAuth avec un fournisseur tiers comme Authelia.
C’est exactement ce que j’ai constaté : le WebUI fonctionnait pour staladmin, mais plus aucun compte ne pouvait s’authentifier en SMTP.
Je pensais, à tort, que seul le compte d’administration passerait par Authelia.
Ce que j’ai retenu
En édition communautaire, pour protéger l’administration d’un serveur de messagerie :
- Rester sur l’annuaire interne et activer le 2FA sur les comptes administrateurs (solution que j’ai gardée). Prévoir un moyen de récupération si l’application TOTP est perdue.
- Protéger
/adminpar Authelia au niveau du reverse proxy (auth_request/ forward auth), en laissant hors de la règle les endpoints des clients mail. - Licence Enterprise si l’assignation d’annuaire par domaine est indispensable.
L’OIDC global n’a de sens que si tous les comptes peuvent s’authentifier par jeton, ce qui n’est pas le cas des clients mail usuels.
Pièges rencontrés
- « Invalid username or password » / log
Unsupported credentials type for OIDC backend: un mot de passe a été envoyé à l’annuaire OIDC, qui n’accepte que des jetons. Chez moi, l’identité autorisée côté Authelia n’était pas celle que je saisissais. /.well-known/oauth-authorization-serverdécrit toujours le serveur d’autorisation propre à Stalwart : ce n’est pas un signe d’erreur. Pour le WebUI, le bon test est/api/discover/<adresse>./.well-known/oauth-protected-resourcepeut continuer à nommer Stalwart lui-même : il concerne les clients mail, pas le WebUI.- Documentation et version : la documentation en ligne est plus récente que mon CLI. Des champs comme
firstPartyouacceptedAudiencesrenvoientunknown field. - Authelia et 2FA : si vous retentez le SSO, enregistrez plusieurs méthodes (clé matérielle WebAuthn, passkey, TOTP en secours), testez-les chacune, et ne changez plus le domaine d’Authelia ensuite.
