Skip to content

Identity: enrich external logins UI with provider details #182

@antosubash

Description

@antosubash

Summary

Improve the Manage/ExternalLogins page so users (and admins) see meaningful information about each linked provider — display name, email at the provider, when it was linked, and a "primary" indicator — rather than the bare provider name we surface today.

Why we need this

The existing page calls userManager.GetLoginsAsync(user) and renders the result, which is a list of UserLoginInfo objects with three fields: LoginProvider, ProviderKey, ProviderDisplayName. We display the provider name and a remove button. That is the bare minimum the API returns.

In practice, users with multiple linked accounts run into real confusion:

  • "I have two Google logins linked — which is my work and which is personal?" Today there's no way to tell from the UI without unlinking and re-linking.
  • "When did I link this? Was it me or someone who got into my account?" No timestamp.
  • "If I unlink this provider, can I still sign in?" — there's no warning that unlinking the only login method while having no password set will lock the user out (HasPasswordAsync check exists in the backend but the UI doesn't lean on it).
  • For admins triaging support: "tell me about the GitHub login on this account" requires querying the DB directly.

Most of the additional information is already available — we just don't capture or render it.

How the user will use it

On Manage/ExternalLogins, each linked provider shows:

  1. Provider logo + display name (e.g., GitHub icon + "GitHub").
  2. Account identifier at the provider — usually the email or username we received in the external login claims (e.g., alice@gmail.com, @alice-gh). Pulled from the principal at link time and stored in a small extension table or as a claim on the user. Without this, two Google entries are indistinguishable.
  3. Linked on: timestamp.
  4. Remove button. If the user has no password set AND this is their only login, the button is disabled with a tooltip: "Set a password before removing your last sign-in method."
  5. A "Link another account" section listing available providers from GetExternalAuthenticationSchemesAsync, which already works — just polish the layout.

For admins, mirror this on the user edit security tab so they can see at a glance which third-party accounts are tied to a user.

Implementation notes

  • The UserLoginInfo returned by GetLoginsAsync doesn't include timestamps or email — Identity's IdentityUserLogin table doesn't have those columns. Two options:
    • Option A (preferred): at link time, also persist a small companion record (ExternalLoginMetadata table: UserId, LoginProvider, ProviderKey, LinkedAt, ProviderEmail, ProviderDisplayName). Owned by the Users module. Populate in ExternalLoginEndpoint callback.
    • Option B: store the metadata as authentication tokens via SetAuthenticationTokenAsync (related to the auth-tokens issue). Lighter schema impact but slightly awkward semantics.
    • Recommend Option A for clarity and queryability.
  • ExternalLoginEndpoint already has access to the ExternalLoginInfo with claims; pull ClaimTypes.Email and ClaimTypes.Name into the metadata at link time.
  • Update Manage/ExternalLoginsEndpoint to join logins with the metadata table.
  • Front-end: update Manage/ExternalLogins.tsx to render the richer cards. Include the "last login method" guard: if !hasPassword && logins.length === 1, disable remove with the tooltip.
  • Provider icons: @simplemodule/ui likely has access to a Lucide icon set; otherwise use a small map of known providers → svg.
  • Audit event ExternalLoginUnlinkedEvent already fires presumably; add ExternalLoginLinkedEvent if it doesn't.
  • Tests: linking a provider populates metadata; removing the only login method without a password fails with a clear error (defense in depth — the UI disables it, but the backend must too).

Benefits

  • Eliminates ambiguity for users with multiple logins of the same kind.
  • Prevents the "removed my only login method, locked out" failure mode at both UI and API layers.
  • Gives support / admins a much richer view of an account's auth surface.
  • All changes are additive — existing flows keep working.

Acceptance criteria

  • New ExternalLoginMetadata (or equivalent) storage records linked-on, provider email, provider display name at link time.
  • Manage/ExternalLogins shows enriched cards: icon, identifier, linked-on, remove (guarded).
  • Backend rejects removing the only login method when the user has no password set, with a clear error code.
  • Admin user edit security tab surfaces the same info.
  • Audit events fire on link and unlink.
  • Integration tests for metadata population at link time and the "last login method" guard.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions