Post

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.

Stalwart : connexion au WebUI via Authelia (OIDC)

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

  1. Dans le formulaire de connexion, on saisit l’adresse complète (staladmin@example.org), sans mot de passe.
  2. Le WebUI interroge /api/discover/<adresse> : si l’annuaire est OIDC, le serveur répond avec les endpoints du fournisseur (Authelia).
  3. Le navigateur est redirigé vers Authelia, puis revient sur /admin/oauth/callback avec un code. L’échange du code contre le jeton est un appel direct du navigateur vers Authelia : il faut donc le CORS.
  4. 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 : avec none, Authelia émet des jetons opaques, que Stalwart refuse par défaut.
  • offline_access et refresh_token : d’après des retours sur le forum de Stalwart, le WebUI demande ce scope. Si Authelia répond invalid_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 :

  1. 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.
  2. Protéger /admin par Authelia au niveau du reverse proxy (auth_request / forward auth), en laissant hors de la règle les endpoints des clients mail.
  3. 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-server dé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-resource peut 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 firstParty ou acceptedAudiences renvoient unknown 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.
Cet article est sous licence CC BY 4.0 par l'auteur.