12 KiB
Data Model: External Authentication
Conventions
- IDs are opaque strings generated by Elsa and never reused.
TenantId = "*"means host-wide/tenant-agnostic;TenantId = ""means only Elsa's default tenant.- 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 | Immutable Connection ID |
TenantId |
string | *, empty default tenant, or concrete tenant |
Key |
string | Stable URL/presentation key; normalized; unique with TenantId among database rows |
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 |
IsDefault |
bool | At most one effective automatic default per scope |
IsEnabled |
bool | Administrative intent; requires validity to be effective |
ArchivedAt |
DateTimeOffset? | Archive is logical deletion |
UnlinkedPolicy |
PolicySelection? | Null means deployment default |
PermissionGrantSources |
list | Ordered and descriptor-validated |
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:
(TenantId, Key). - Creation/update additionally rejects an existing configuration-owned
(TenantId, Key). - A tenant key may not collide with an inherited host-wide key in its effective registry.
Idsurvives archive/restore. Restore returns disabled and requires revalidation.- Display-only changes advance
Revisionbut may retainMaterialRevision. - Adapter/trust settings, scope, Secret Binding generation, policy, claim projection, grants, or enable/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.
SecretBinding
| Field | Type | Rules |
|---|---|---|
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.
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 and CreateUser.
GrantSourceSelection
| Field | Type | Rules |
|---|---|---|
Type |
string | Installed, deployment-allowed Permission Grant Source |
SettingsVersion |
int | Positive schema version |
Settings |
JSON object | Provider-owned validation/migration |
Order |
int | Stable evaluation order |
V1 types are Elsa role grants, claim mappings, and group mappings. Stored permission strings remain open vocabulary, but actor delegation and deployment boundaries are validated before save.
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.
ExternalIdentityLink
Associates one provider identity with one tenant-owned Elsa User.
| Field | Type | Rules |
|---|---|---|
Id |
string | Immutable |
TenantId |
string | Resolved target tenant; never * for a user link |
ConnectionId |
string | Immutable Identity Provider Connection ID |
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 tenant |
CreatedAt |
DateTimeOffset | Creation/prelink time |
LastSignedInAt |
DateTimeOffset? | Safe operational metadata |
Constraints
- Unique index:
(TenantId, ConnectionId, Issuer, SubjectHash). - Foreign key to
User; broker additionally 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 and atomically reserves a globally unique internal User.Name, sets the resolved tenant, leaves password fields null, and creates the External Identity Link in the same transaction.
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 | Resolved before redirect |
ConnectionId |
string? | 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 |
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 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 |
ExternalAuthenticationSession
| Field | Type | Rules |
|---|---|---|
Id |
string | Included as safe session claim in access tokens |
TenantId |
string | Resolved target tenant |
UserId |
string | Same-tenant Elsa User |
ConnectionId |
string | Source connection |
ConnectionMaterialRevision |
string | Revision at full sign-in |
Issuer |
string | Validated issuer |
SubjectHash |
string | No raw subject |
ExternalGrants |
JSON array | Permission strings plus source provenance; no raw claims |
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 |
RefreshGeneration |
long | Compare-and-swap rotation counter |
RevokedAt |
DateTimeOffset? | Explicit or reuse-detection revocation |
RevocationReason |
string? | Safe category |
Refresh atomically verifies current token hash and generation, rotates the token, reevaluates current Elsa-owned role grants, and retains ExternalGrants. Reuse of a superseded token revokes the session.
ConnectionObservation
Latest on-demand test result only.
| Field | Type | Rules |
|---|---|---|
ConnectionId |
string | 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 |
ConnectionId |
string | Draft connection |
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 |
PermissionProjection |
JSON array | Proposed grants and provenance |
Warnings |
list | Safe bounded warnings |
ExpiresAt |
DateTimeOffset | Default 10 minutes |
ConsumedAt |
DateTimeOffset? | One-time read |
PermissionDescriptor
Runtime metadata only; not persisted by this feature.
| Field | Type | Rules |
|---|---|---|
Name |
string | Elsa permission string |
DisplayName |
string | Optional authoring label |
Description |
string? | Optional |
Category |
string? | Optional grouping |
SourceModule |
string | Provenance |
Descriptors may be incomplete. Their absence never invalidates a permission string.