elsa-core/specs/012-external-authentication/contracts/studio-contract.md
2026-07-25 02:35:56 +02:00

14 KiB

Studio Contract: External Authentication

Packages

src/modules/
├── Elsa.Studio.Settings/
├── Elsa.Studio.Authentication.UI/
├── Elsa.Studio.ExternalAuthentication/
├── Elsa.Studio.ExternalAuthentication.BlazorServer/
└── Elsa.Studio.ExternalAuthentication.BlazorWasm/

Shared Module

Elsa.Studio.Settings owns UI composition/navigation contracts only. It does not define a server Settings entity, generic Settings persistence, or authorization semantics.

Elsa.Studio.Authentication.UI owns:

  • The generic /login and /logout shell.
  • Login Method chooser layout, accessibility, safe return paths, and error presentation.
  • Authentication UI contribution contracts for local, brokered external, and future methods.
  • Generic authentication navigation without provider-specific business logic.

Elsa.Studio.ExternalAuthentication contributes:

  • Refit clients and API DTOs.
  • Broker-backed Login Method provider and local/external contributions to the Authentication.UI shell.
  • PKCE request model and external-authentication-specific state.
  • Identity Provider Connection list/editor/test/preview.
  • External Identity Link page and bounded user picker.
  • Descriptor-driven field renderer and optional custom-editor registry.
  • Settings navigation contribution for Connections and Security contributions for Links/Sessions.
  • Common broker token/provider abstractions.

Registration:

services.AddExternalAuthenticationModule(backendApiConfig);

The module registers management only when the remote feature is available. It never replaces the Authentication.UI shell; the selected brokered host package activates its contributions.

UI Contribution Contracts

public interface ILoginMethodCatalog;
public interface ILoginMethodComponentProvider;
public interface ILoginMethodIconProvider;

public interface ISettingsNavigationContributor
{
    ValueTask ContributeAsync(
        SettingsNavigationBuilder builder,
        CancellationToken cancellationToken = default);
}

public interface ISecurityMenuContributor
{
    ValueTask ContributeAsync(
        SecurityMenuBuilder builder,
        CancellationToken cancellationToken = default);
}

ILoginMethodCatalog supplies ordered method metadata, ILoginMethodComponentProvider resolves method-specific UI, and ILoginMethodIconProvider resolves trusted local icons. Settings/Security contributors add navigation metadata only. None replace the shell, create duplicate parents, bypass route/API authorization, or persist arbitrary Settings data.

Server Host Package

Elsa.Studio.ExternalAuthentication.BlazorServer owns:

  • ElsaStudio.ExternalAuthentication.Cookie scheme.
  • Protected server-side broker transaction state.
  • /authentication/external/callback.
  • /authentication/external/logout-callback.
  • Back-channel code exchange and refresh.
  • Server token provider for API clients and SignalR.
  • Unauthorized component that redirects to /login.
  • Cookie/session expiration and logout.

Registration:

services.AddExternalAuthenticationBroker(options =>
    configuration.GetSection("Authentication:ExternalAuthentication").Bind(options));

Cookie requirements:

  • Name: ElsaStudio.ExternalAuthentication.
  • HttpOnly = true.
  • Secure = Always.
  • SameSite = Lax.
  • No Elsa or provider token readable by browser code.
  • Antiforgery required on state-changing BFF endpoints.
  • Session lifetime does not exceed the external session.

The callback performs client-state and PKCE transaction validation before sending the Elsa completion code back-channel.

WebAssembly Host Package

Elsa.Studio.ExternalAuthentication.BlazorWasm owns:

  • Browser-crypto PKCE verifier/challenge generation.
  • Pre-exchange transaction state in sessionStorage, deleted after callback.
  • /authentication/external/callback.
  • /authentication/external/logout-callback.
  • Direct public-client code exchange and refresh.
  • In-memory token provider by default.
  • API/SignalR authorization integration.
  • Unauthorized component that redirects to /login.

Registration uses the same method name with host-specific overload/assembly:

services.AddExternalAuthenticationBroker(options =>
    configuration.GetSection("Authentication:ExternalAuthentication").Bind(options));

No client secret is accepted. Persistent post-exchange browser token storage is an explicit deployment opt-in and displays a startup/security warning.

Authentication Provider Selection

The host reads Authentication:Provider:

Value Existing/new behavior Broker chooser
ElsaIdentity Existing Elsa username/password Studio authentication Off
OpenIdConnect Existing direct single-provider OpenID Connect Off
ElsaLogin Existing legacy login module Off
ExternalAuthentication New Elsa broker client On

Server retains its existing default (ElsaIdentity); WebAssembly retains its existing default (OpenIdConnect). Broker mode is explicit opt-in.

Startup fails when:

  • Authentication:Provider = ExternalAuthentication but broker client ID/callback is missing.
  • Broker mode attempts to register a public client on Server or confidential secret on WebAssembly.
  • Direct OpenID Connect handlers are also enabled in broker mode.
  • Callback, logout callback, or browser origin does not match the server-side Authentication Client registration.

Direct settings remain untouched for rollback.

Login Chooser Contribution

Route: /login

Flow:

  1. Resolve the validated client-local returnPath; invalid values become /.
  2. Fetch anonymous Login Methods with ILoginMethodsApi.
  3. Render all available local and external methods in deterministic order.
  4. Order and emphasize the preferred method without automatic redirect.
  5. Generate PKCE before initiating either local or external broker flow.
  6. Preserve only opaque client transaction state and local return path.

Presentation:

  • Reuse BasicLayout, branding, and localization.
  • Render a visible method name even if an icon fails.
  • Resolve only trusted icon IDs through a local asset registry.
  • Full keyboard operation, visible focus, accessible names, and status/error announcements.
  • Do not render provider HTML, remote images, authority, or client identifiers.

Shared Refit Clients

public interface ILoginMethodsApi;
public interface IExternalAuthenticationBrokerApi;
public interface IIdentityProviderConnectionsApi;
public interface IExternalIdentityLinksApi;
public interface IExternalAuthenticationSessionsApi;
  • ILoginMethodsApi uses the anonymous backend API provider.
  • Broker exchange uses a dedicated client without an existing bearer handler.
  • Management, link, and session clients use the authenticated backend API provider.
  • DTOs mirror rest-api.md and do not define alternate Studio-only payloads.

Management Information Architecture

Settings
└── SSO

Security
├── External Identity Links
└── External Authentication Sessions

Routes:

  • /settings/sso-connections
  • /settings/sso-connections/new
  • /settings/sso-connections/{connectionId}
  • /security/external-identity-links
  • /security/external-authentication-sessions

Legacy connection-route aliases may remain for bookmarked links, but generated navigation and documentation use /settings/sso-connections.

Elsa.Studio.Settings owns Settings navigation and ISettingsNavigationContributor; External Authentication contributes one-level SSO. Elsa.Studio.Security owns Security and ISecurityMenuContributor; External Authentication contributes Links and capability-gated Sessions. Neither menu grants authorization.

Connection List

The list is server-paged and filters by:

  • Search.
  • Source.
  • Adapter type.
  • Enabled intent.
  • Validity.
  • Override/shadow state.
  • Archive state.

Columns show display name/key, record ID, source/override relationship, adapter, enabled intent, validity, latest on-demand test with timestamp/revision/staleness, preferred state, and revision.

Behavior:

  • Configuration-owned rows are inspect-only and offer Create Studio Override when authorized.
  • Overrides visibly identify the configuration baseline and state that no fields are merged.
  • Disabled overrides keep shadowing; archived overrides reveal configuration; restore resumes shadowing.
  • Archived rows permit restore only when authorized.
  • Caller permissions control action visibility, but every API remains authoritative.

Connection Editor

Sections:

  1. Identity: immutable Connection Key, display name, icon, order, preferred. The connected server environment is implicit and no target field is shown.
  2. Provider adapter: installed adapter and descriptor-driven fields.
  3. Upstream Client Registration: exact discoveryUrl, confidential client ID, client_secret_basic/client_secret_post, Managed/External Secret Binding, read-only deployment-derived callback, and scopes.
  4. Advanced Provider Trust: permission/deployment-gated overrides for discovery-derived issuer, authorization/token endpoints, and signing keys. The section is collapsed by default, labels values unsafe, requires confirmation on save, and keeps a persistent warning while any override is active.
  5. Claim projection: allowed/redacted claims and size limits.
  6. Admission and create-user roles: per-connection policy; one External User Matcher and Reject/CreateUser no-match action for matcher policy; static defaultRoleIds only for CreateUser; role-assignment warnings.
  7. Authorization explanation: matchers propose users, never roles; Elsa Roles produce permissions and no claim-role/permission mapping controls are rendered in v1.
  8. Logout: capability-driven mode.
  9. Status: enabled intent, validity, source/shadowing, latest test, revision.

Advanced trust controls never expose switches for callback derivation, confidential-client mode, S256 PKCE, state/correlation/nonce, signature validation, audience/lifetime validation, one-time codes, or secret redaction.

Secret fields show only configured/resolvable state. Managed Secrets have replace/remove actions; External Secrets show deployment-owned reference/state and no value-lifecycle action. The editor never binds a returned secret value.

Save uses If-Match. On 412, Studio preserves unsaved values, loads the current safe model, and offers reload or manual reapply—never silent overwrite.

Test and Preview

Test

  • Requires test permission.
  • Shows progress, then redacted category, summary, warnings, duration, correlation ID, tested revision, and timestamp.
  • Never labels the result continuous health.
  • Immediately marks a result stale after a material edit.

Preview Sign-in

  • Requires preview permission and explicit confirmation that no normal session/user/link will be created.
  • Opens the Elsa-owned preview navigation URL.
  • On return, reads the result once from the initiating active administrator session.
  • Displays masked identity, allowlisted preview claims, proposed match/no-match policy decision, safe proposed user/link action, static create-user roles when applicable, and warnings. Matcher-required claims are not retained in the result.
  • If the administrator session is gone, display an expired result and do not retry/read it through anonymous state.

The dedicated page:

  • Uses the tenant-scoped paginated user option API.
  • Filters links by user and connection.
  • Displays connection, issuer, masked subject hint, created time, and last sign-in.
  • Creates prelinks from explicit issuer and subject input.
  • Requires confirmation before unlink.
  • Never displays provider tokens, raw stored subject, full claims, credential fields, roles, or permission lists.

The user-link panel is a reusable component for a future user detail page.

Session Administration

When the server advertises session administration:

  • List safe session metadata by user/connection/status.
  • Revoke with confirmation and reason.
  • Never display access/refresh tokens, hashes, raw subject, or claim snapshot.

Hide the section when the remote capability is absent.

UI Authorization Matrix

Capability UI behavior
Read only Show list/detail; no mutation buttons
Create Show New
Update Edit database rows; enable/disable; secret binding actions
Archive Archive/restore
Test Test button
Preview Preview button
Policy manage Policy/grant editors
Unsafe provider trust Unsafe controls with warning/confirmation
Link manage Link page and mutations
Session read/revoke Session section and revoke

Route authorization and API authorization are both required. Menu visibility alone grants nothing.

Role Lifecycle UI Boundary

External Authentication contributes the backend Role-deletion dependency and remediation contract but no Role-management page or additional Settings route. A future Elsa Roles UI MAY call the deletion-impact and remediation APIs, display configuration paths and last-default-role warnings, and collect the required confirmations. Until such a UI exists, the contract remains available to authorized API clients.

Studio Verification

  • Unit tests for descriptor form mapping, safe secret state, menu contribution, authorization affordances, preferred ordering without redirect, icon fallback, return-path validation, ETag recovery, and stale test labels.
  • Server integration tests for protected transaction state, callback, back-channel exchange, cookie flags, server-only tokens, refresh, logout, API/SignalR token attachment, and conflicting startup mode.
  • Browser tests for WebAssembly redirect round-trip, PKCE, exact-origin CORS, no client secret, transaction cleanup, memory token loss on reload, and chooser accessibility.
  • Cross-repository tests run both Studio hosts against the same fake provider and Core broker fixture.