Browser-direct linked identities

Provider links belong to a tenant member within an organization, not just the current application. All routes below are SSO POSTs below the browser-direct base, with selected session handles, admitted Origin and fresh endpoint DPoP. They need no additional profile/email scope. The application and member must remain active/admitted. Starting/completing provider linking and provider sign-in require current federation policy. Listing and unlink through another retained method are not a blanket federation-policy override; a provider can serve as retained proof only while currently permitted.

Relative path Additional input Success
/identities/list None Organization identity view
/identities/start Authorization context plus provider: github, discord or m7 transaction, state, url for provider navigation
/identities/status transaction Ceremony status, provider display and resulting identity ID if any
/identities/confirm transaction, code_verifier status: completed, identity_id plus identity view
/identities/cancel transaction status only, cancelled or already completed
/identities/unlink identity_id local UUID removed, identity view, sessions_revoked, revocation_pending

All rows above include session handles in addition to the listed fields. Start, confirm and unlink require primary authentication no older than five minutes; list/status/cancel do not. Refresh and selection cannot manufacture fresh authentication. The same-carrier reauthentication flow replaces old entries only after successful activation.

The identity view contains sub, scope: organization, identities, allowed providers, reauthentication_required, can_link, multiple_linked_identities_allowed, and message. Identity rows expose only local id, provider, display handle, linked_at, last_login, verified and can_unlink. Raw provider subjects, remote credentials and tokens are absent.

{"bd_carrier":"<carrier>","bd_session":"<session>","bd_key":"<carrier secret>","identity_id":"eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee"}

That body unlinks only the reviewed local identity; never construct it from a provider display handle. For linking, start with new state/PKCE but the selected session's signing key. Follow url in a full page or popup. The callback returns identity=continue,state or an error. Validate state and call status. The status projection has status (draft, ready, completed, cancelled), provider (null or {vendor,handle}), and identity_id (null or UUID).

sequenceDiagram
  participant B as Browser
  participant S as SSO
  participant P as Provider
  B->>S: identities/start + context + selected handles
  S-->>B: transaction + url
  B->>P: Fresh provider authentication
  P->>S: Validated provider result
  S-->>B: Callback identity=continue + state
  B->>S: identities/status
  S-->>B: ready + provider display
  Note over B: Explicitly confirm reviewed link
  B->>S: identities/confirm + verifier + handles
  S-->>B: completed + local identity ID

Provider proof alone does not insert a link. The ten-minute ceremony binds org, member, app, origin, key and the original selected session. Explicit confirm requires the original verifier and current policy/ownership; a changed session requires restarting. Completed confirm is a receipt retry, even if its original freshness window has passed, while current selected-session authority remains required. After a lost response use status before creating anything else. Cancel before completion discards the pending proof; cancelling a completed ceremony returns its completed receipt and does not undo the link. Expiry needs a new ceremony.

Provider subject ownership is unique within the organization. An existing identity owned by another member is never moved, even if email or display names match. With org tenant_policy_allow_multiple_linked_identities off, the first federated link is allowed but additional ones are blocked. Password and email do not count. Existing multiple links remain usable. On permits up to 16 federated links. Current policy is rechecked before insertion, including during a previously started ceremony. See org policy.

Unlink requires fresh proof of a sign-in method that remains after deletion: a native password, another currently permitted verified provider, a consumer link, or eligible email sign-in with current unique verified ownership and policy. You cannot sign in through the provider being removed and use that alone to remove it. At least one usable method must remain.

Successful unlink returns removed: true and the refreshed view. Repeating for an already-absent ID returns removed: false; it cannot remove a replacement identity by provider name. A successful link does not rotate tokens or close sessions. Unlink immediately closes sessions attributed to the removed identity and legacy unattributed tenant sessions, across hosted SSO and all applications. Known sessions from other retained methods remain. sessions_revoked counts local closures; revocation_pending distinguishes unfinished remote token-family cleanup. Each operation attempts a bounded batch of eight remote revocations; the existing reconciliation process handles remaining work. It does not imply immediate application-cookie clearing or offline JWT invalidation everywhere.

Malformed IDs produce invalid_request; unavailable/expired transactions can produce invalid_grant. identity_transaction_invalid (400) covers rejected provider confirmation/verifier/phase. identity_unavailable is 403 for denied link availability or 409 for a current ownership/limit/service conflict. remaining_signin_method_required (403) needs fresh proof of a retained method; reauthentication_required (403) needs primary sign-in again. session_changed (409) requires the original selected session or a new ceremony; provider policy denial is access_denied (403). Reload the view instead of choosing another member or suppressing denial. Network errors need status/list reconciliation with a fresh proof. No provider token or proof should be logged.

Managers can inspect and unlink using separate manager authority and a recorded reason. New identity linking remains member self-service. Manager last-method protection does not count email alone as a retained method; do not reuse self-service eligibility in a manager UI.