Skip to content

feat: OIDC/OAuth2 authentication integration for Horde base - #110

Open
jcdelepine wants to merge 2 commits into
horde:FRAMEWORK_6_0from
jcdelepine:feat/OidcIntegration
Open

jcdelepine wants to merge 2 commits into
horde:FRAMEWORK_6_0from
jcdelepine:feat/OidcIntegration

Conversation

@jcdelepine

Copy link
Copy Markdown
Contributor
Wires the OIDC auth driver (horde/Core) into the Horde base application:

- OAuthLoginController: accept GET in addition to POST to support
  automatic redirect from login.php to the IdP.

- login.php: auto-redirect to the configured OIDC provider when
  auth.driver = oidc and auth.params.redirect_provider is set.
  Redirects on all logout reasons except explicit auth failures
  (REASON_BADLOGIN, REASON_FAILED, REASON_LOCKED).

- LoginServiceFactory: register OidcPreLogoutHandler as a pre-logout
  handler when the auth driver is oidc.

- LoginService::performLogout(): aggregate redirect URLs from
  pre-logout handlers; handler redirect takes priority over
  redirect_on_logout config.

- OAuthAccountController: resolve a human-readable Horde username from
  OIDC userinfo claims (preferred_username, uid, login, email);
  store tokens for XOAUTH2 hooks; filter login.php as post-login
  redirect destination.

- OAuthTokenRepositoryFactory: SQL-backed token repository.

- Admin UI: OIDC-specific provider fields (logout strategy, SLO
  endpoints, XOAUTH2 domain).

- conf.xml: OIDC auth driver configuration and OAuth/OIDC token
  storage tab.

- Migration 3: add OIDC-specific columns to horde_oauth_providers.

- hooks.php.dist: smtp_credentials and backchannel logout route.

Depends on:
- horde/Core: OIDC integration (PR #160)
- horde/base refactor/login-logout-delegate-to-service (PR #109)

@ralflang
ralflang self-requested a review June 15, 2026 09:51
@ralflang ralflang self-assigned this Jun 15, 2026
@ralflang

Copy link
Copy Markdown
Member

As anticipated in #109 - This is somewhat more complicated to handle than the previous PRs on the Oauth library itself.

Different architectures

Your model of integrating OIDC works a bit different than what we started to implement. I understand yours already works correctly for your install and delivers OAUTH (for UI login) and XOAUTH (for backend login) and a seamless integration.

That makes it a bit hard for me to defend wanting to do things a bit different.

Fundamentally, the upstream model makes OAuth 2.0 / OIDC an alternative to an Auth driver. Your model makes it an auth driver. In our model, OIDC capabilities (beyond "login to horde") can be added to apps and services individually and independent of login method. I.e. we can drive service login to Twitter, Github, Mastodon, Google Calendar ... independent of how to login to Horde. At the moment many of the necessary changes in horde are not yet implemented, i.e. activesync and caldav/carddav backends with OAuth 2.0 support.

Gaps when OIDC is the auth backend

  • User listing/searching
  • activesync (at least for now)
  • caldav/carddav (at least for now)
  • JSON-RPC & SOAP
  • LDAP without an "admin" bind credential.

Cherry Picking

I would like to take a lot of your PRs' supporting code because it does mostly what we need to do across both models.
Other parts I would like to rework piecemeal to fit into both models

Username

As an opt-in mechanism creating/matching an auth backend username to an attribute provided by the IdP would also work in the upstream concept.

Compromise on OAuth Login driver

I see there's room for the OAuth Login driver as the primary/only login mechanism for some installations. Horde's login.php will forward to the IdP screen transparently. This will however hide mode selection and language selection as well as the secondfactor UI.

Timeline

I already stretched the Horde 6.0 Stable Release timeline for some last minute fixes to the cache and activesync codebase. I would rather not stretch much further.

@TDannhauer How do you see this? This could easily be an addition for an early horde/base 6.1 as it's a major feature.

@TDannhauer

Copy link
Copy Markdown
Contributor

To be honest, I prefer a soon release of Horde.
On large scale, H6 is overdue for years, and I think we layed a good foundation to continue with bugfixes and further larger changes will be then in 6.1, might it be Auth topics or ActiveSync.

Reading the techn question being a Auth driver or beeing an Alternative to an with driver: what is the technical difference? Is adding another "Module Type" worth it?

@jcdelepine

Copy link
Copy Markdown
Contributor Author

@ralflang @TDannhauer Thank you for the detailed feedback. I understand the architectural
concerns and agree that keeping OAuth orthogonal to the auth driver
is the cleaner long-term approach.

Looking at what actually blocked me: when logging in via OAuth with a
non-OIDC auth driver, IMP returned "User is not authorized for
Mail". That error is what led me to write the OIDC auth driver in the
first place.

The root cause is that IMP_Auth::_canAutoLogin() only attempts XOAUTH2
when auth.driver = oidc. A minimal change — trying OAuth2 tokens first
regardless of the auth driver, falling back to hordeauth when none are
available — would allow IMP to work with LDAP/SQL/IMP auth drivers while
OAuth tokens are present. Ingo/Sieve already works this way through the
transport_credentials hook, without any special auth driver.

This would allow the hook infrastructure (OidcHookHelper,
imap_preauthenticate, smtp_credentials, transport_credentials) to land
independently of the auth driver question. The OIDC auth driver,
OidcPreLogoutHandler and the backchannel logout controller can wait for
when the broader session invalidation mechanism is ready.

Does this direction make sense to you?

@ralflang

Copy link
Copy Markdown
Member

Yes, this direction makes sense to me. Allow me some time to look into how we can best integrate what your hooks do with the federated approach.

@TDannhauer There is quite some difference.

With OIDC as the "auth driver" it will be responsible for all aspects of user handling and everything OIDC naturally cannot do will be unaccessible. With the model we designed for, we can have both "real users of this platform" and "collaborators/privileged guests". We can also allow social login from different platforms (github, google, university internal OIDC) while still having one specific account.

The main problem is we historically used a concept of "application auth" (which delegates nicely to horde auth most of the time) which defined both if the user can use the application and how to login to backends (smtp, imap, sieve, ldap to some degree, gollem samba backends, ...) and increasingly this is model doesn't fit very well.

These days many services and resources horde needs to access live in cloud platforms which don't want username/password style logins. They want Oauth2.0/OIDC (everything google calendar/tasks/adressbook, social media, billing platforms) or XOauth (Imap, SMTP, ... ) which is OAuth but funneled into a traditional protocol's password field.

Jean Charles Delépine added 2 commits September 16, 2026 22:05
login.php handled logout logic directly (CSRF check, clearAuth,
session setup, redirect). LoginService.performLogout() already
encapsulates all of this. Delegate to it so logout behaviour is
consistent whether the request comes from login.php or from the
modern routing stack (ResponsiveLogoutController).

Pre-logout handlers registered in LoginService now run on all
logout paths, not just those going through the responsive stack.
Wires OIDC/OAuth2 support into the Horde base application, independent
of the Horde auth driver (LDAP, SQL, application, etc. all supported):

- OAuthLoginController: accept GET in addition to POST to support
  direct links/buttons to the IdP.

- LoginServiceFactory: register OidcPreLogoutHandler whenever OAuth/OIDC
  providers are configured — a user authenticating via LDAP/SQL/etc. may
  still hold OAuth tokens (e.g. for XOAUTH2 IMAP access) that need
  revocation/SLO on logout. No dependency on auth.driver.

- LoginService::performLogout(): aggregate redirect URLs from
  pre-logout handlers; handler redirect takes priority over
  redirect_on_logout config.

- OAuthAccountController: resolve a human-readable Horde username from
  OIDC userinfo claims (preferred_username, uid, login, email);
  store tokens for XOAUTH2 hooks; filter login.php as post-login
  redirect destination.

- OAuthTokenRepositoryFactory: SQL-backed token repository.

- Admin UI: OIDC-specific provider fields (logout strategy, SLO
  endpoints, XOAUTH2 domain).

- conf.xml: OAuth/OIDC token storage tab under Authentication.

- Migration 3: add OIDC-specific columns to horde_oauth_providers.

- hooks.php.dist: smtp_credentials and backchannel logout route.

An earlier revision of this branch also auto-redirected to the IdP
when auth.driver was set to a dedicated 'oidc' value. That driver no
longer exists — this integration works alongside any existing auth
driver. An admin wanting to skip the login page entirely can already
do so via the pre-existing generic alternate_login config.

Depends on:
- horde/Core: OIDC integration (PR horde#160)
- horde/base refactor/login-logout-delegate-to-service (PR horde#109)
@jcdelepine

Copy link
Copy Markdown
Contributor Author

Hi Ralf,

Sorry for the long silence on this — following up now that things have settled.

I've reworked this branch (and the related ones in Core/imp/ingo) to drop the dedicated Horde_Core_Auth_Oidc driver entirely, per your feedback and the Two Actor model from base#70. Auth is now handled by whichever driver is already configured (LDAP, application, etc.); OAuth/OIDC tokens are held and validated orthogonally, independent of that driver:

  • LoginServiceFactory registers OidcPreLogoutHandler whenever OAuth/OIDC providers are configured — not tied to auth.driver anymore.
  • Each app that needs XOAUTH2 validates token presence itself against the backend it's actually connected to (see imp's IMP_Application::authValidate() / IMP_Auth::validateOauth(), wired through the existing validate auth capability).
  • The old auth.driver = oidc config case and its login.php auto-redirect are gone. An admin who wants to skip the credentials form entirely can still do it through the existing generic alternate_login config.

Updated PRs, all rebased onto current FRAMEWORK_6_0:

Along the way I also found two unrelated Core/base bugs while working through the DI container and the login form — filed as separate PRs since they don't depend on any of the above:

And for the record: #190 is resolved and merged. #218 has a working fix in #227 — I know you'd rather land a cleaner long-term approach there, so no rush on my end regardless of when that follow-up lands.

Let me know if the current shape still misses something from the Two Actor model, happy to keep iterating.

Jean Charles

@ralflang

ralflang commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Hi JCDelepine,

thank you for updating this. This is much closer to what I intend and for application auth it matches well.
For service auth we are amending core and base OIDC handling currently. I will release the documentation soon.

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.

3 participants