elsa-workflows/elsa-core · error · InvalidOperationException

Authentication client is unavailable.

Error message

Authentication client is unavailable.

What it means

A guard in ExternalAuthenticationBroker's callback handling: the authentication client needed to complete the broker flow (e.g. the OIDC/OAuth client for the connection) could not be obtained — typically because the connection or its provider configuration is missing or disabled — so the callback is failed rather than processed.

Solutions

  1. Verify the external authentication connection exists, is enabled, and its provider/client configuration is complete.
  2. Re-initiate the broker flow after fixing the configuration; the in-flight transaction cannot proceed without a client.
  3. Check that the authentication feature providing the client was registered and started.
Defensive patterns

Strategy: fallback

When it happens

Trigger: Thrown at src/modules/Elsa.ExternalAuthentication/Services/ExternalAuthenticationBroker.cs:535 when the library encounters an invalid state.

Common situations: See trigger scenarios.

Understand the failure class


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/e3e59f52da9757de. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication/Services/ExternalAuthenticationBroker.cs:535

        if (taken is not TakeResult<BrokerTransaction>.Taken { Value: var transaction })
            return await CallbackOutcomeAsync(BrokerCallbackResult.Fail(BrokerErrorFactory.Create(taken is TakeResult<BrokerTransaction>.Expired ? BrokerErrorCategory.FlowExpired : BrokerErrorCategory.InvalidRequest)), "logout", "callback", null, connectionKey, cancellationToken);
        if (!string.Equals(transaction.ConnectionKey, ConnectionRevisionCalculator.NormalizeKey(connectionKey), StringComparison.Ordinal))
            return await CallbackOutcomeAsync(BrokerCallbackResult.Fail(BrokerErrorFactory.Create(BrokerErrorCategory.InvalidRequest)), "logout", "callback", transaction.TenantId, connectionKey, cancellationToken);
        var connection = await connectionRegistry.FindByIdAsync(transaction.TenantId, transaction.ConnectionId!, cancellationToken);
        return connection is null || connection.IsShadowed || !connection.Connection.IsEnabled || connection.Connection.ArchivedAt is not null || !string.Equals(connection.Connection.MaterialRevision, transaction.ConnectionMaterialRevision, StringComparison.Ordinal)
            ? await CallbackOutcomeAsync(BrokerCallbackResult.Fail(BrokerErrorFactory.Create(BrokerErrorCategory.FlowChanged)), "logout", "callback", transaction.TenantId, connectionKey, cancellationToken)
            : await CallbackOutcomeAsync(BrokerCallbackResult.Redirect(transaction.CallbackUri), "logout", "callback", transaction.TenantId, connectionKey, cancellationToken);
    }

    public async ValueTask<BrokerLogoutResult> ContinueLogoutAsync(string handle, CancellationToken cancellationToken = default)
    {
        var taken = await stateStore.TryTakeAsync<BrokerTransaction>("UpstreamLogoutContinue", Hash(handle), cancellationToken);
        if (taken is not TakeResult<BrokerTransaction>.Taken { Value: var transaction } || !Uri.TryCreate(transaction.ReturnPath, UriKind.Absolute, out var navigation))
            return await LogoutOutcomeAsync(BrokerLogoutResult.Fail(BrokerErrorFactory.Create(BrokerErrorCategory.InvalidRequest)), "continue", cancellationToken);
        return await LogoutOutcomeAsync(BrokerLogoutResult.Navigate(navigation), "continue", cancellationToken);
    }

    private AuthenticationClient EnsureClient(string clientId) => options.Value.Clients.FirstOrDefault(x => x.IsEnabled && string.Equals(x.ClientId, clientId, StringComparison.Ordinal)) ?? throw new InvalidOperationException("Authentication client is unavailable.");
    private static void ValidateAuthorizationRequest(BrokerAuthorizationRequest request, AuthenticationClient client)
    {
        if (!string.Equals(request.ResponseType, "code", StringComparison.Ordinal) || !string.Equals(request.CodeChallengeMethod, "S256", StringComparison.Ordinal) || !client.CallbackUris.Contains(request.RedirectUri) || !ClientReturnPathValidator.TryValidateForClient(request.ReturnPath, client.AllowedReturnPathPrefixes, out _))
            throw new InvalidOperationException("The broker authorization request is invalid.");
    }
    private static bool VerifyPkce(string challenge, string? verifier) => !string.IsNullOrWhiteSpace(verifier) && string.Equals(challenge, Base64Url(SHA256.HashData(Encoding.ASCII.GetBytes(verifier))), StringComparison.Ordinal);
    private static string CreateOpaqueValue() => Base64Url(RandomNumberGenerator.GetBytes(32));

    private string Hash(string value) => handleHasher.Hash(value);
    private static string Base64Url(byte[] value) => Convert.ToBase64String(value).TrimEnd('=').Replace('+', '-').Replace('/', '_');
    private static Uri AppendCallbackParameters(Uri uri, string code, string? clientState)
    {
        var result = AppendQuery(uri, "code", code);
        return string.IsNullOrWhiteSpace(clientState) ? result : AppendQuery(result, "state", clientState);
    }
    private static Uri AppendQuery(Uri uri, string key, string value) { var separator = string.IsNullOrEmpty(uri.Query) ? "?" : "&"; return new(uri + separator + Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value), uri.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative); }
    private async ValueTask<BrokerCallbackResult> FailTrustedCallbackAsync(BrokerTransaction transaction, BrokerErrorCategory category, CancellationToken cancellationToken)
    {

View on GitHub (pinned to fe9217bdfa)