elsa-workflows/elsa-core · error · InvalidOperationException

OpenID Connect settings must be an object.

Error message

OpenID Connect settings must be an object.

What it means

Thrown by OpenIdConnectSettingsV1Migration.MigrateAsync when the persisted v1 OpenID Connect settings JSON does not parse into a JSON object. The migration upgrades stored settings (v1 -> v2) by moving 'authority' to 'discoveryUrl' and setting PKCE/client-auth defaults, and it refuses to operate on non-object payloads.

Solutions

  1. Open the persisted settings JSON and make it a proper object containing the v1 fields (authority, clientId, etc.) before re-running migration.
  2. If the settings value is unrecoverable, delete/replace the record and re-enter the OpenID Connect configuration in the Studio/admin UI so it is saved at the current schema version.
  3. Verify which storage row/workflow definition carries the corrupt payload (query the settings column and inspect its JSON type).
  4. Re-export/import workflows ensuring the settings node is serialized as an object, not a stringified or scalar value.

Example fix

// persisted settings before
"settings": "https://idp.example.com"
// after
"settings": { "authority": "https://idp.example.com", "clientId": "app" }
Defensive patterns

Strategy: validation

Validate before calling

using var doc = JsonDocument.Parse(settingsRaw);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
    throw new InvalidOperationException("OIDC settings must be a JSON object before migration");

Try / catch

try { migrated = await migration.MigrateAsync(settings, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("must be an object"))
{
    logger.LogError(ex, "Persisted OIDC settings are corrupt; re-enter the configuration");
}

Prevention

When it happens

Trigger: MigrateAsync receives a JsonElement whose ValueKind is not Object (e.g. null, array, string, number) — typically because a workflow-definition or configuration store holds corrupt or wrongly-shaped settings JSON under the OpenID Connect activity/feature key.

Common situations: Manual edits to exported workflow JSON; an upgrade/import wrote settings as a bare string; an earlier tool truncated or replaced the settings object with a scalar; migrating definitions created by another module into an OIDC field.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectSettingsV1Migration.cs:17

using System.Text.Json;
using System.Text.Json.Nodes;
using Elsa.ExternalAuthentication.Contracts;

namespace Elsa.ExternalAuthentication.OpenIdConnect.Services;

/// <summary>Migrates the unreleased v1 authority/callback settings to the v2 deployment-derived callback model.</summary>
public sealed class OpenIdConnectSettingsV1Migration : IAdapterSettingsMigration
{
    public string AdapterType => OpenIdConnectExternalAuthenticationAdapter.AdapterType;
    public int FromVersion => 1;
    public int ToVersion => 2;

    public ValueTask<JsonElement> MigrateAsync(JsonElement settings, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        var node = JsonNode.Parse(settings.GetRawText())?.AsObject() ?? throw new InvalidOperationException("OpenID Connect settings must be an object.");
        if (!node.ContainsKey("discoveryUrl") && node["authority"]?.GetValue<string>() is { Length: > 0 } authority)
            node["discoveryUrl"] = authority.TrimEnd('/') + "/.well-known/openid-configuration";
        node.Remove("authority");
        node.Remove("callbackUri");
        node["providerPkce"] = "required";
        node["clientAuthenticationMethod"] ??= "client_secret_basic";
        using var document = JsonDocument.Parse(node.ToJsonString());
        return ValueTask.FromResult(document.RootElement.Clone());
    }
}

View on GitHub (pinned to fe9217bdfa)