I would like to authenticate users that use the PLAIN mechanism against an oauth2 idp (in this case it's an Azure AD B2C).
I configured dovecot.conf with

auth_mechanisms = plain oauthbearer xoauth2
passdb {
  driver = oauth2
  mechanisms = plain login
  args = /etc/dovecot/dovecot-oauth2.plain.conf.ext
}


and in /etc/dovecot/dovecot-oauth2.plain.conf.ext I have

grant_url = https://simons0f7b2c.b2clogin.com/simons0f7b2c.onmicrosoft.com/B2C_1_ROPC_Auth/oauth2/v2.0/token?scope=xxxxx-5be3-405a-babb-xxxx
client_id = xxxxx-5be3-405a-babb-xxxx
client_secret = m6xxxxxxxx
username_attribute = extension_PreferredLoginUsername
#introspection_url = http://localhost:8000/introspect
#introspection_mode = post
use_grant_password = yes
debug = yes
pass_attrs = host=127.0.0.1 proxy=y proxy_mech=xoauth2 pass=%{oauth2:access_token}


I can see that dovecot sends a request to simons0f7b2c.b2clogin.com that looks like this:

POST /simons0f7b2c.onmicrosoft.com/B2C_1_ROPC_Auth/oauth2/v2.0/token?scope=xxxxx-5be3-405a-babb-xxxx HTTP/1.1
Host: simons0f7b2c.onmicrosoft.com
Date: Tue, 04 Oct 2022 15:43:42 GMT
User-Agent: dovecot-oauth2-passdb/2.3.16
Content-Length: 169
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=sp12@31337.it&password=<userpass>&client_id=xxxxx-5be3-405a-babb-xxxx&client_secret=m6xxxxxxxx

and the response is something like

HTTP/1.1 200 OK
Cache-Control: no-store, must-revalidate, no-cache
Content-Type: application/json; charset=utf-8
Set-Cookie: x-ms-cpim-trans=; domain=simons0f7b2c.b2clogin.com; expires=Thu, 04-Oct-2012 15:29:04 GMT; path=/; SameSite=None; secure; HttpOnly
x-ms-gateway-requestid: 6ce588cf-9e35-491e-8393-18da8ccbe2cd
X-Frame-Options: DENY
Public: OPTIONS,TRACE,GET,HEAD,POST
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Allow: OPTIONS
Allow: TRACE
Allow: GET
Allow: HEAD
Allow: POST
Date: Tue, 04 Oct 2022 15:29:03 GMT
Content-Length: 1003

{"access_token":"eyJ0xxxxx9uIw","token_type":"Bearer","expires_in":"3600"}


Decoding the access_token I can see that there is a field extension_PreferredLoginUsername:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4"
}.{
  "iss": "https://simons0f7b2c.b2clogin.com/xxxxx/v2.0/",
  "exp": 1664898983,
  "nbf": 1664895383,
  "aud": "xxxxx-5be3-405a-babb-xxxx",
  "idp": "LocalAccount",
  "sub": "969xxx7",
  "extension_PreferredLoginUsername": "s2",
  "tfp": "B2C_1_ROPC_Auth",
  "azp": "89xxxc0",
  "ver": "1.0",
  "iat": 1664895383
}


so the field extension_PreferredLoginUsername is there, but dovecot says:

dovecot: auth: Debug: http-client: conn 40.126.31.64:443 [1]: Got 200 response for request [Req1: POST https://simons0f7b2c.b2clogin.com/simons0f7b2c.onmicrosoft.com/B2C_1_ROPC_Auth/oauth2/v2.0/token?scope=xxxxx-5be3-405a-babb-xxxx]: OK (took 1016 ms + 327 ms in queue)
dovecot: auth: Debug: oauth2(sp12@31337.it,192.168.0.4,<p0vv9jbq3MrAqAAE>): Password grant succeeded
dovecot: auth: Debug: oauth2(sp12@31337.it,192.168.0.4,<p0vv9jbq3MrAqAAE>): Processing field access_token
dovecot: auth: Debug: oauth2(sp12@31337.it,192.168.0.4,<p0vv9jbq3MrAqAAE>): Processing field token_type
dovecot: auth: Debug: oauth2(sp12@31337.it,192.168.0.4,<p0vv9jbq3MrAqAAE>): Processing field expires_in
dovecot: auth: Error: oauth2(sp12@31337.it,192.168.0.4,<p0vv9jbq3MrAqAAE>): oauth2 failed: Password grant failed: No username returned
dovecot: auth: Debug: oauth2(sp12@31337.it,192.168.0.4,<p0vv9jbq3MrAqAAE>): Finished passdb lookup
dovecot: auth: Debug: pam(sp12@31337.it,192.168.0.4,<p0vv9jbq3MrAqAAE>): Performing passdb lookup



So, why does dovecot say "No username returned"? The username is there! Do I have to configure something else?