elsa-core/src/modules/Elsa.ExternalAuthentication
Sipke Schoorstra 815d7b7f70
Fix MemoryRoleStore global role ID uniqueness (#8127)
* fix(identity): enforce global role ID uniqueness

* fix(identity): preserve role tenant on updates

* test(identity): cover role tenant rehoming
2026-09-13 23:28:38 +02:00
..
Constants Add external authentication broker 2026-07-24 19:04:26 +02:00
Contracts refactor(auth)!: retire the legacy permission constants and duplicate descriptor types (#7987) 2026-08-25 06:04:32 +02:00
Endpoints refactor(auth)!: retire the legacy permission constants and duplicate descriptor types (#7987) 2026-08-25 06:04:32 +02:00
Extensions fix(external-authentication): scope role-deletion impact to the role's tenant (#8036) 2026-09-06 22:34:44 -07:00
Features Add external authentication broker 2026-07-24 19:04:26 +02:00
Models refactor(auth)!: retire the legacy permission constants and duplicate descriptor types (#7987) 2026-08-25 06:04:32 +02:00
Notifications Add atomic external identity link replacement 2026-07-26 23:40:17 +02:00
Options fix(external-auth)!: wildcard-aware permission grant boundary, and startup smoke tests for both hosts (#7985) 2026-08-25 05:25:29 +02:00
Permissions refactor(auth)!: retire the legacy permission constants and duplicate descriptor types (#7987) 2026-08-25 06:04:32 +02:00
Policies fix(external-auth)!: wildcard-aware permission grant boundary, and startup smoke tests for both hosts (#7985) 2026-08-25 05:25:29 +02:00
Providers fix(external-auth)!: wildcard-aware permission grant boundary, and startup smoke tests for both hosts (#7985) 2026-08-25 05:25:29 +02:00
Services Fix MemoryRoleStore global role ID uniqueness (#8127) 2026-09-13 23:28:38 +02:00
ShellFeatures fix(external-auth)!: wildcard-aware permission grant boundary, and startup smoke tests for both hosts (#7985) 2026-08-25 05:25:29 +02:00
Stores Enforce unique external refresh token hashes (#8095) 2026-09-13 01:10:32 -07:00
Validation fix(auth): validate wildcard permission patterns and warn on deny-list stripping (#7997) 2026-08-27 11:45:54 +02:00
Elsa.ExternalAuthentication.csproj Harden external authentication contracts and flows 2026-07-25 03:48:50 +02:00
README.md Allow promoting shadowed authentication connections 2026-07-29 12:13:06 +02:00

Elsa External Authentication

Elsa.ExternalAuthentication is the protocol-neutral broker for Elsa-owned external sign-in. It composes deployment-installed provider adapters, connection sources, unlinked-identity policies, permission grant sources, secret resolvers, atomic flow stores, and Elsa token issuance.

OpenID Connect support and the Elsa Secrets bridge are separate optional packages. The broker does not retain provider tokens and does not treat external claims as Elsa permissions unless an explicitly configured grant source maps or bounds them.

Registration

services.AddElsa(elsa =>
{
    elsa.UseExternalAuthentication(feature =>
    {
        feature.ConfigureOptions = options =>
            configuration.GetSection("ExternalAuthentication").BindExternalAuthenticationOptions(options);
    });
});

services.AddOpenIdConnectExternalAuthentication();

Use BindExternalAuthenticationOptions for IConfiguration binding so the arbitrary JSON envelopes used by adapter, policy, and grant-source settings are reconstructed from their configuration sections. Direct programmatic option configuration is unaffected.

AddExternalAuthenticationServices supplies in-memory stores suitable for single-node development. A multi-node deployment must replace broker state, grants, sessions, observations, registry versions, and identity links with shared durable implementations, share ASP.NET Core Data Protection keys, and configure the same HandleHashing:SharedKeyBase64 on every node.

Configuration ownership

  • ExternalAuthentication:Connections defines immutable, configuration-owned connections.
  • Database-owned connections are optional and controlled by EnableDatabaseConnections.
  • Configuration takes precedence over a database connection with the same effective key and scope. Studio shows the database row as shadowed instead of silently overwriting it.
  • When AllowConfigurationConnectionOverrides is enabled, an administrator can promote an unarchived shadowed database connection into an explicit override, preserving that record, its secret bindings, and its lifecycle. A promotion that would remove the final normal sign-in path is rejected by the final-login-path guard.
  • Authentication Clients, extension allowlists, permission boundaries, egress policy, and final-login recovery policy remain deployment-owned.

An empty AllowedAdapterTypes collection permits every installed adapter. The built-in policy allowlist contains reject and create-user; the built-in grant-source allowlist contains elsa-roles, claim-mapping, group-mapping, and claim-pass-through.

Secure defaults

Setting Default
Local broker login Enabled
Database connections Enabled
Unlinked identity policy reject
Broker transaction / completion code 10 minutes / 1 minute
Preview / maximum external session 10 minutes / 8 hours
Provider HTTPS Required
Private-network provider destinations Denied
Provider redirects At most 3, revalidated on every hop
Provider request/connect timeout 10 seconds
Broker client PKCE S256 required
WebAssembly credential policy Memory
Upstream logout Disabled
Final-login-path guard Enabled; recovery method required
Session administration Enabled
ASP.NET Core health-check bridge Disabled

The separately registered health check is tagged external-authentication and optional; it is not a readiness dependency by default.

Secret bindings

The foundation includes the configuration resolver, which reads deployment-owned secrets from standard .NET configuration. It is registered automatically by AddExternalAuthenticationServices; no additional package or custom ISecretBindingResolver is required. A binding contains only the configuration key, never the secret value:

{
  "ExternalAuthentication": {
    "AuthenticationClients": [
      {
        "clientId": "elsa-studio-server",
        "displayName": "Elsa Studio Server",
        "clientType": "confidential",
        "callbackUris": [
          "https://localhost:7113/authentication/external/callback"
        ],
        "logoutCallbackUris": [
          "https://localhost:7113/authentication/external/logout-callback"
        ],
        "allowedReturnPathPrefixes": ["/"],
        "secretBinding": {
          "ownership": "external",
          "resolverType": "configuration",
          "reference": "Secrets:ExternalAuthentication:StudioServerClientSecret"
        },
        "isEnabled": true
      }
    ]
  },
  "Secrets": {
    "ExternalAuthentication": {
      "StudioServerClientSecret": "<development-secret>"
    }
  }
}

The equivalent environment variable is:

Secrets__ExternalAuthentication__StudioServerClientSecret=<deployment-secret>

Any standard IConfiguration provider can supply the referenced value, including environment variables, Kubernetes-mounted configuration, and cloud secret providers. In a CShells feature block, feature settings are exposed under the feature name; for example, a value nested at Features:ExternalAuthentication:Secrets:StudioServerClientSecret is referenced as ExternalAuthentication:Secrets:StudioServerClientSecret. After removing the value from appsettings.json, its environment-variable equivalent is ExternalAuthentication__Secrets__StudioServerClientSecret.

Configuration bindings use ownership: external: their values and lifecycle remain deployment-owned and read-only in Studio. The optional Elsa Secrets bridge instead uses ownership: managed with resolver type elsa-secrets; it resolves active Elsa Secrets by name and allows authorized administrators to manage their lifecycle through Elsa. Public responses expose only configured/resolvable state for either binding type and never return secret values.

Operations

Management, descriptor, link, preview, test, and session APIs are served below /external-authentication. On-demand tests store only the latest redacted observation and become stale after a material connection revision. Preview state and results are short-lived, administrator-bound, and one-time; preview never creates a user, link, Elsa credential, or normal session.

See the full quickstart and REST contract.