elsa-core/specs/012-external-authentication/data-model.md
2026-08-02 03:35:39 +02:00

15 KiB

Data Model: External Authentication

Conventions

  • Stable connection record IDs identify management resources and transient broker/preview/observation records. Immutable logical Connection Keys identify durable External Identity Links and long-lived External Authentication Sessions.
  • Connections are host-wide within the currently connected server environment; no persisted Deployment Target/Server Environment field exists.
  • Timestamps are UTC DateTimeOffset.
  • Revisions are monotonically increasing 64-bit integers.
  • Secret values, provider tokens, completion codes, refresh tokens, raw subjects, and unrestricted claims are never stored in public connection documents.
  • JSON fields are opaque, versioned extension settings. Their provider owns validation and migration.

IdentityProviderConnection

Represents one database-owned provider trust relationship. Configuration-owned connections use the same domain model but are materialized from options and never written through the store.

Field Type Rules
Id string Stable record ID used by management and transient broker records
ConnectionKey string Immutable normalized logical key; globally unique within the host target
Ownership enum Configuration, Studio, or StudioOverride
OverridesConfiguration bool True only for an explicit complete override of the same key
AdapterType string Must identify an installed, deployment-allowed adapter
AdapterSettingsVersion int Positive schema version
AdapterSettings JSON object Opaque to registry; adapter validates/migrates
SecretBindings map<string, SecretBinding> Names declared by descriptor; contains no values
DisplayName string Required, bounded, safe plain text
IconId string? Trusted server asset identifier only
DisplayOrder int Deterministic tie-break by normalized key
IsPreferred bool At most one effective preferred method; presentation only, never auto-redirect
IsEnabled bool Administrative intent; requires validity to be effective
ArchivedAt DateTimeOffset? Archive is logical deletion
UnlinkedPolicy PolicySelection? Null means deployment default
DefaultRoleIds set Applied only to JIT-created users; actor-authorized on save
MatcherPolicy MatcherPolicySettings? Present only for the matcher-based Unlinked Identity Policy
ClaimProjection ClaimProjection Allowlist, value limits, and redaction policy
UpstreamLogoutMode enum Disabled, UserChoice, or Always
Revision long Changes on every mutation; concurrency token
MaterialRevision string Non-secret fingerprint of all sign-in-affecting state
CreatedAt / UpdatedAt DateTimeOffset Audit metadata

Constraints

  • Database unique index on ConnectionKey for active/disabled Studio documents in one environment.
  • A configuration collision is accepted only through the explicit Studio Override operation.
  • An override is a full document; there is no field-level source merge.
  • A disabled override continues shadowing. An archived override reveals configuration. Restore resumes shadowing and returns disabled.
  • ConnectionKey survives archive/restore and is immutable.
  • Display-only changes advance Revision but may retain MaterialRevision.
  • Adapter settings, Secret Binding generation, policy/matcher settings, claim projection, static create-user roles, override state, enablement, or archive state affect MaterialRevision.

State Transitions

Draft/Disabled --validate + enable--> Enabled
Enabled --disable------------------> Disabled
Draft/Disabled/Enabled --archive---> Archived
Archived --restore-----------------> Disabled

Invalid or unresolved configuration never becomes effectively enabled. Test observations do not change lifecycle state.

For an override, Disabled still shadows configuration, Archived reveals configuration, and restore resumes the shadow in Disabled.

SecretBinding

Field Type Rules
Ownership enum Managed or External
ResolverType string Installed, deployment-allowed resolver
Reference string Non-secret lookup key
ExpectedType string? Optional resolver/type constraint
ExpectedScope string? Optional resolver/scope constraint

Resolution returns a transient value and opaque nonreversible generation fingerprint. The fingerprint contributes to material revision but is not exposed or stored in API models.

Managed bindings are lifecycle-managed through Elsa Secrets and may be replaced/removed through authorized APIs. External bindings use the built-in configuration-key resolver or another deployment resolver and remain value-read-only in Studio.

PolicySelection

Field Type Rules
Type string Installed, deployment-allowed Unlinked Identity Policy
SettingsVersion int Positive schema version
Settings JSON object Provider-owned validation/migration

Built-in types are Reject, CreateUser, and the generic matcher-based policy.

MatcherPolicySettings

Field Type Rules
MatcherType string One installed, deployment-allowed IExternalUserMatcher
MatcherSettingsVersion int Positive schema version
MatcherSettings JSON object Matcher-owned validation/migration
NoMatchAction enum Reject or CreateUser

The matcher descriptor declares required normalized claim types. Those claims are supplied ephemerally and discarded after evaluation. One match proposes an existing user; no match follows NoMatchAction; ambiguous results and errors reject. V1 ships no Elsa first-party verified-email matcher. DefaultRoleIds apply only when CreateUser executes.

ClaimProjection

Field Type Rules
AllowedClaimTypes set Only these normalized claims survive sign-in processing
RedactedClaimTypes set Values hidden in preview/diagnostics
MaximumClaimCount int Secure bounded default; deployment may lower
MaximumValueLength int Per scalar/array member
MaximumTotalBytes int Entire normalized projection

Values are only strings or string arrays. Every value carries adapter/provider provenance during evaluation. Claims outside the projection are discarded.

Associates one provider identity with one tenant-owned Elsa User. Connection deployment remains host-wide in v1.

Field Type Rules
Id string Immutable
TenantId string Broker-resolved target Elsa tenant
ConnectionKey string Immutable logical connection key
Issuer string Validated canonical issuer namespace
SubjectHash string Keyed hash of canonical provider subject
SubjectHint string? Optional masked operator hint; never the raw subject
UserId string Elsa User in the same target tenant
CreatedAt DateTimeOffset Creation/prelink time
LastSignedInAt DateTimeOffset? Safe operational metadata

Constraints

  • Unique index: (TenantId, ConnectionKey, Issuer, SubjectHash).
  • Foreign key to User; broker enforces tenant equality.
  • Connection archive preserves links.
  • Explicit unlink removes the active association and emits a notification.
  • Concurrent JIT and prelink use one atomic create-link-or-get-existing operation.

User Migration

The existing User entity changes:

Field Change
HashedPassword Nullable; both password fields are present or absent together
HashedPasswordSalt Nullable

Credential-less users cannot authenticate through legacy or broker-local password validation. Existing password-backed rows require no data change.

JIT provisioning generates a globally unique internal User.Name, leaves password fields null, and assigns authorized default/matcher roles in the User-store write. The link store independently arbitrates the external identity tuple; a losing or failed link writer removes only the credential-less User it created before returning or propagating the failure.

AuthenticationClient

Deployment-configured registration for an Elsa client.

Field Type Rules
ClientId string Unique immutable identifier
DisplayName string Operator-facing
ClientType enum Confidential or Public
CallbackUris set Exact HTTPS matches; development loopback may be explicitly allowed
LogoutCallbackUris set Exact matches
AllowedOrigins set Required for public clients; no wildcard
AllowedReturnPathPrefixes set Segment-boundary prefixes for post-authentication client-local navigation
SecretBinding SecretBinding? Required for confidential clients; forbidden for public clients
IsEnabled bool Disabled clients cannot initiate or exchange

Authentication Clients are not Elsa API Applications and contain no roles or permissions.

BrokerTransaction

Shared, protected, short-lived provider/local/preview correlation state.

Field Type Rules
HandleHash string Keyed hash of random browser-visible state handle
Purpose enum ExternalSignIn, LocalSignIn, Preview, UpstreamLogout
ClientId string Registered Authentication Client or preview purpose
CallbackUri URI Exact registered callback
ReturnPath string Validated client-local path
TenantId string Broker-resolved target Elsa tenant
ConnectionId string? Connection record ID required for external/preview
ConnectionMaterialRevision string? Captured effective revision
SecretGenerationFingerprint string? Protected, nonreversible
PkceChallenge string S256 only
ProviderNonce string? External/preview
ProtectedPayload bytes Data-protected adapter state
ExpiresAt DateTimeOffset Default 10 minutes
ConsumedAt DateTimeOffset? Atomic single-use marker

EF persistence also stores the normalized ExpiresAtUtcTicks companion used in the same compare-and-swap predicate as ConsumedAt, because not every supported provider can order DateTimeOffset values directly.

The public handle contains no protected payload. Atomic take transitions pending to consumed; expired or mismatched state is never revived.

AuthorizationGrant

Single-use Elsa completion code record.

Field Type Rules
CodeHash string Keyed hash of random code
ClientId string Bound client
CallbackUri URI Bound exact callback
TenantId string Bound target Elsa tenant
UserId string Resolved Elsa User
ExternalSessionId string? Null for broker-local sign-in
PkceChallenge string S256
ExpiresAt DateTimeOffset Default 60 seconds
ConsumedAt DateTimeOffset? Atomic single use

EF persistence uses the same normalized ExpiresAtUtcTicks companion for the atomic consume predicate.

ExternalAuthenticationSession

Field Type Rules
Id string Included as safe session claim in access tokens
TenantId string Resolved target Elsa tenant
UserId string Elsa User
ConnectionKey string Source logical connection
ConnectionMaterialRevision string Revision at full sign-in
Issuer string Validated issuer
SubjectHash string No raw subject
ProvisionedRoleIds JSON array Redacted JIT provenance; not reapplied to existing users
UpstreamLogoutArtifact protected bytes? Minimal adapter artifact only when configured logout requires it; never an access/refresh token by default
StartedAt DateTimeOffset Full external sign-in
LastRefreshedAt DateTimeOffset Rotation time
ExpiresAt DateTimeOffset Maximum session age; default eight hours
RefreshExpiresAt DateTimeOffset Inactivity bound
CurrentRefreshTokenHash string? Keyed hash of current opaque token; absent until the first refresh token is issued
RefreshGeneration long Compare-and-swap rotation counter
RevokedAt DateTimeOffset? Explicit or reuse-detection revocation
RevocationReason string? Safe category

EF persistence stores the optional current hash in a one-to-one ExternalAuthenticationSessionRefreshTokens row so the unissued state requires no sentinel value. Refresh atomically verifies current token hash and generation, rotates the token, and reevaluates current Elsa-owned role grants. It does not re-query upstream claims or mutate user roles. Reuse of a superseded token revokes the session.

ConnectionObservation

Latest on-demand test result only.

Field Type Rules
ConnectionId string Connection record ID; primary key
TestedMaterialRevision string Determines freshness
ObservedAt DateTimeOffset UTC
Status enum Succeeded, Failed, Warning
Category string Stable redacted category
Duration TimeSpan Test duration
Summary string Safe bounded message
Warnings list Safe bounded warnings
CorrelationId string Diagnostic correlation

An observation is stale when its tested revision differs from the current effective material revision. No history table is required.

PreviewResult

Field Type Rules
HandleHash string One-time result handle
AdministratorId string Initiating authenticated actor
TenantId string Preview target Elsa tenant
ConnectionId string Draft connection record ID
MaterialRevision string Exact previewed revision
Issuer string Validated issuer
MaskedSubject string Never raw subject
ProjectedClaims JSON object Allowlisted and descriptor-redacted only
PolicyDecision JSON object Proposed action; no mutation
UserResolutionProjection JSON object Proposed match/no-match action, safe user hint, and static create-user roles; no matcher claims
Warnings list Safe bounded warnings
ExpiresAt DateTimeOffset Default 10 minutes
ConsumedAt DateTimeOffset? One-time read

EF persistence uses the same normalized ExpiresAtUtcTicks companion for the atomic consume predicate.

OpenIdConnectAdapterSettings v2

Field Type Rules
DiscoveryUrl URI Exact absolute HTTPS discovery-document URL
ClientId string Confidential upstream client identifier
ClientAuthenticationMethod enum Serialized as client_secret_basic or client_secret_post
Scopes set Must include openid; bounded and validated
AdvancedTrustOverrides JSON object? Optional issuer, authorization/token endpoints, and signing keys; permission/deployment-gated and persistently warned

The callback is not stored in adapter settings. Elsa derives it from deployment external base address, fixed callback route, and escaped immutable Connection Key. Advanced overrides replace discovery inputs but cannot disable signature/issuer/audience/lifetime validation. Confidential-client mode and provider PKCE S256 are immutable.