Skip to content

feat(provisioning): authenticate provisioned accounts with the user's OIDC token (XOAUTH2)#13311

Open
LeahAuroraV wants to merge 4 commits into
nextcloud:mainfrom
LeahAuroraV:feat/oidc-xoauth2
Open

feat(provisioning): authenticate provisioned accounts with the user's OIDC token (XOAUTH2)#13311
LeahAuroraV wants to merge 4 commits into
nextcloud:mainfrom
LeahAuroraV:feat/oidc-xoauth2

Conversation

@LeahAuroraV

@LeahAuroraV LeahAuroraV commented Jul 17, 2026

Copy link
Copy Markdown

OIDC Token Yoink

This PR extends the provisioning functionality of nextcloud mail to work with access and refresh tokens obtained on login through user_oidc.

There's a new "use the user's OIDC token" toggle on a provisioning config (in the groupware section under admin settings). accounts then authenticate via XOAUTH2 with no stored password. This token is mirrored from the user_oidc (first provider) session on interactive requests, and refreshed via the stored refresh token for background/cron sync (so it keeps working after the access token expires). It's kinda similar to how the existing Google/Microsoft integrations are already working for this app. The toggle is disabled and validation rejects it when user_oidc isn't installed, and the integration will stay disabled when user_oidc is not present.

It was developed with authentik as central identity provider to which both nextcloud and dovecot/postfix are connected. Dovecot is configured in such a way that it will accept XOAUTH2 as authentication. It has been verified against the local dev setup mentioned below, as well as a more extensive testing environment that adds ldap as user directory for the mail servers to the mix.

Because this feature requires quite a big dev setup I've added documentation to get a test environment up and running: doc/oidc-xoauth2.md

Changes

  • New OidcIntegration service that mirrors the user_oidc session token onto the account and refreshes it via the stored refresh token for background sync (lib/Integration/OidcIntegration.php, with Psalm stubs for the optional user_oidc classes so there's no hard dependency).
  • Migration adding a nullable oidc_enabled column to mail_provisionings, plus the entity/mapper field and validation (existing configs are untouched).
  • Provisioning wiring that provisions matching accounts as xoauth2 with no password and seeds/refreshes the token per request (Service/Provisioning/Manager.php, Http/Middleware/ProvisioningMiddleware.php).
  • Token refresh before send: a new BeforeSmtpClientCreated event (IMAP already had something similar), dispatched by the SMTP factory and handled by the existing OAuth refresh listener alongside Google/Microsoft.
  • Admin UI toggle in the provisioning settings, disabled with a hint when user_oidc isn't installed (Settings/AdminSettings.php + the two Vue components), plus unit tests for the new paths.

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

… OIDC token (XOAUTH2)

Add an oidcEnabled option to provisioning configurations. Accounts are
provisioned with auth_method=xoauth2 and no stored password:

- The user_oidc login token is mirrored into the account row on
  interactive requests (ExternalTokenRequestedEvent) and refreshed at
  the IdP token endpoint with the stored refresh token for background
  jobs, mirroring the Google/Microsoft integrations.
- SMTP client creation dispatches a new BeforeSmtpClientCreated event
  so tokens are also refreshed before sending.
- The option is guarded end to end: the admin UI toggle, provisioning
  validation and the integration itself require the user_oidc app to
  be installed and enabled.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Leah <leahvenema@hotmail.com>
Document the oidcEnabled provisioning option end to end: the Authentik
provider + federation setup, the Dovecot oauth2 passdb and Postfix
submission config, the Nextcloud occ/provisioning steps, verification
(including the background refresh path) and troubleshooting. Includes a
ready-to-run local Dovecot + Postfix pair for the docker-dev environment.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Leah <leahvenema@hotmail.com>
@ChristophWurst

Copy link
Copy Markdown
Member

Thanks for the PR

@ChristophWurst ChristophWurst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick architectural review

Thank you

* The IdP token endpoint and client credentials are read from the user_oidc provider
* configuration; this integration is inert when user_oidc is not installed.
*/
class OidcIntegration {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To manage the complexity of this change I'd favor getting individual OIDC support in first (and close #12491), and then build OIDC provisioning on top.

@LeahAuroraV LeahAuroraV Jul 17, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick review!

So basically create an oauth client within the mail app in which users can choose OpenID as option when adding a mailbox, with a well-known discovery url (or the individual endpoints that would contain), client id and client secret, and then have the open id authentication open in a new tab?

Then the provisioning on top would be defining those urls, client id and secret in the admin settings, rather than using the the user_oidc app.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or would the clients always be global settings?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have XOAUTH2 support for Google and Microsoft. Admins set the client id and secret. When a user sets up an account we detect if oauth should be used based on the IMAP server.

For OIDC I could imagine the same. Admin sets client id, secret and server hostname. When a user then creates a new account for that hostname it will open the auth popup for that server.

*/
public function isAvailable(): bool {
return $this->appManager->isEnabledForAnyone('user_oidc')
&& class_exists(\OCA\UserOIDC\Event\ExternalTokenRequestedEvent::class);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\OCA* is the private API of another app. This is not supposed to be used by other apps to avoid inter app dependencies. The clean way is going through a OCP public API that lives in Nextcloud server and has a strong stability guarantee.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/nextcloud/user_oidc/blob/main/lib/Event/ExternalTokenRequestedEvent.php does specifically mention in its docblock that it is meant for external apps.

// Psalm stubs for the optional user_oidc app (https://github.com/nextcloud/user_oidc).
// Only the surface used by OCA\Mail\Integration\OidcIntegration is declared.

namespace OCA\UserOIDC\Model {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without a mechanism to update these stups they give a false sense of safety. Psalm will continue to pass in our repository when the user_oidc APIs change (break).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants