Skip to content

OpenID Connect links: remove one in the panels, and stop using them when the identity provider changes - #913

Merged
KrzysztofPajak merged 3 commits into
developfrom
feature/oidc-link-management
Oct 15, 2026
Merged

KrzysztofPajak merged 3 commits into
developfrom
feature/oidc-link-management

Conversation

@KrzysztofPajak

Copy link
Copy Markdown
Member

Resolves: no issue (follow-up to the OpenID Connect work, #891-#910)
Type: feature

Issue

Two gaps around the stored links between customers and identities at the OpenID Connect provider (ExternalAuthentication, ExternalAuth.OpenIdConnect):

  • No way to remove one link in the panels. When a person was deleted and created again at the provider, the sign-in failed with "already connected to another identity provider account". In ProviderOnly the customer cannot remove the connection, and the panels only listed it. The guide told administrators to delete the document in the database.
  • Changing OidcResourceServer:Authority kept using the old links. A sub is unique only within one provider. After switching to another provider or realm (or connecting a copy of the production database to a test provider), a subject of the new provider equal to a stored one selected another person's account. Providers that number their users (1, 2, ...) make that likely. The guide only said to remove the links first, without saying how.

Solution

  • Remove one link: Customers > Customers > a customer > Info: the external sign-in grid gets a delete command (Admin and Store panels, Edit permission, within the panel's scope through LoadAuthorizedCustomer). Removing an OpenID Connect link also ends the customer's provider sessions (IOidcSessionService.EndByCustomer, end reason LinkRemoved). The next provider sign-in connects the account again by the rules of a first sign-in.
  • Links belong to one provider:
    • The store records the provider the links were made with in OidcLinkSettings (store-independent). The first use adopts the configured Authority, so existing installations keep working. Trailing slash and case are ignored.

    • When Authority differs, OidcCustomerResolver refuses provider sign-in (OidcLoginRefusal.ProviderChanged, with its own text on the plugin's failure page), access tokens (403) and LinkIdentity (OidcLinkResult.ProviderChanged). An error is logged at most once in five minutes.

    • Configuration > External authentication gets an OpenID Connect account links card, shown while the resource server is enabled. It shows the provider and the number of links, reports a change, and offers:

      • Remove all links (another provider; also available at any time): deletes every OpenID Connect link, ends every open provider session, adopts the configured provider.
      • Keep the links (shown after a change; the same provider at a new address): adopts the configured provider.

      Both buttons ask for confirmation.

  • IOidcLinkService / OidcLinkService hold this logic. OidcSessionService gets EndByCustomer and EndAllOpen.
  • Guide: new section 3.6 (managing links, changing the provider). Section 3.1 point 6 and the troubleshooting table point to it instead of the database.
  • Resources: admin texts in DefaultLanguage.xml and Upgrade/en_250.xml; the plugin's Failed.ProviderChanged in the plugin's list (the plugin is new in 2.5).

Tests:

  • OidcLinkServiceTests: adoption, same provider with trailing slash and case, change, state, remove all, keep, remove one, another customer's link refused.
  • Resolver: token, sign-in and LinkIdentity refused after a change.
  • OidcSessionServiceTests: EndByCustomer, EndAllOpen.
  • BaseCustomerControllerTests: unlink within scope; out of scope throws and removes nothing.

Verified end to end on a running copy (ProviderOnly, Keycloak 26.8 and Zitadel v4.19):

  • The first sign-in adopted the Keycloak authority.
  • The admin deleted a customer's link from the customer page; its sessions ended and the next sign-in linked the account again.
  • After switching Authority to Zitadel:
    • the sign-in was refused (/oidc/failed?reason=7, the browser stays a guest) and the error was logged;
    • the card showed the change;
    • Remove all links removed 5 links and ended 16 sessions, and the Zitadel sign-in then worked;
    • after another change, Keep the links kept the link and the sign-in worked.

Breaking changes

  • IOidcSessionService has two new members (EndByCustomer, EndAllOpen), and OidcCustomerResolver takes IOidcLinkService in its constructor. Both are unreleased in develop only.
  • Installations that change Authority after this release must confirm the links in Configuration > External authentication before provider sign-in works again.

…hen the identity provider changes

- Customers > Info: an external sign-in row can be deleted (Admin and Store panels, within the panel's scope); removing
  an OpenID Connect link also ends the customer's sessions started at the provider.
- The store records the provider the links were made with (OidcLinkSettings; the first use adopts the configured
  Authority). After OidcResourceServer:Authority changes, provider sign-in, access tokens and new links are refused and
  an error is logged, until an administrator removes the links (another provider) or keeps them (the same provider at a
  new address) in Configuration > External authentication. A subject is unique only within one provider, so a stored
  link must never select an account for a user of another provider.
Copilot AI balanced review requested due to automatic review settings October 14, 2026 18:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

private void LogMismatch(string linkedAuthority)
{
var now = DateTime.UtcNow.Ticks;
var last = Interlocked.Read(ref _lastMismatchLogTicks);
…and bulk session end

- Token revocations live in their own collection (OidcTokenRevocation, keyed by a hash of the
  subject), so removing a link no longer brings revoked access tokens back
- Without any OpenID Connect link the configured Authority is adopted; links whose provider was
  never recorded are refused until reviewed instead of being adopted silently
- Recording the provider is serialized per instance and skipped while no Authority is configured
- While the links belong to another provider: provider sessions are not used or revalidated,
  back-channel logout ends no session (it still revokes the subject's tokens), and SsoOnly keeps
  the password
- EndAllOpen ends every open session with one update and one cache prefix removal
- Link counts use CountAsync/AnyAsync; Remove/Keep links require a configured resource server
- The customer tab lists OpenID Connect links without the sign-in plugin
var now = DateTime.UtcNow.Ticks;
var last = Interlocked.Read(ref _lastMismatchLogTicks);
if (now - last < TimeSpan.FromMinutes(5).Ticks ||
Interlocked.CompareExchange(ref _lastMismatchLogTicks, now, last) != last)
@KrzysztofPajak
KrzysztofPajak merged commit bc7008f into develop Oct 15, 2026
6 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the feature/oidc-link-management branch October 15, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants