elsa-workflows/elsa-core · error · InvalidOperationException

The persisted external authentication value could not be…

Error message

The persisted external authentication value could not be deserialized as {typeof(T).Name}.

What it means

Thrown by ExternalAuthenticationJsonSerializer.Deserialize<T> when the persisted string cannot be deserialized into T as a non-null value. The helper uses JsonSerializerDefaults.Web and treats a null result (JSON 'null' literal or shape mismatch producing null) as a hard failure, since a persisted external-authentication payload must always be a valid value of T.

Solutions

  1. Inspect the persisted string and re-serialize it to the expected type T (matching the Web defaults: camelCase, case-insensitive) before use, e.g. Serialize<T>(correctValue).
  2. If the row is stale from a schema change, migrate or delete the row and let the provisioning flow recreate it.
  3. Confirm T matches the type originally stored; deserialize to the legacy type first and map to the new type if a version change occurred.
  4. Guard callers: validate the raw value parses (JsonNode.Parse) and its shape matches T before invoking Deserialize.

Example fix

// before
var state = ExternalAuthenticationJsonSerializer.Deserialize<LinkState>(raw); // raw is 'null'
// after
var state = string.IsNullOrWhiteSpace(raw)
    ? null
    : ExternalAuthenticationJsonSerializer.Deserialize<LinkState>(raw);
if (state is null) { /* recreate the link */ }
Defensive patterns

Strategy: validation

Validate before calling

bool canDeserialize = !string.IsNullOrWhiteSpace(raw)
    && JsonNode.Parse(raw) is not null
    && JsonSerializer.Deserialize<T>(raw, new JsonSerializerOptions(JsonSerializerDefaults.Web)) is not null;

Type guard

static bool IsDeserializableAs<T>(string? raw) where T : class
{
    if (string.IsNullOrWhiteSpace(raw)) return false;
    try { return JsonSerializer.Deserialize<T>(raw, new JsonSerializerOptions(JsonSerializerDefaults.Web)) is not null; }
    catch (JsonException) { return false; }
}

Try / catch

try { value = ExternalAuthenticationJsonSerializer.Deserialize<T>(raw); }
catch (InvalidOperationException ex) when (ex.Message.Contains("could not be deserialized"))
{
    logger.LogWarning(ex, "Persisted payload corrupt; recreating");
    value = null; // fall back to recreating the persisted state
}

Prevention

When it happens

Trigger: Calling Deserialize<T> on a column value that is the JSON literal 'null', or whose JSON shape does not match T (e.g. stored JSON for a different type, empty object where a record with required properties is expected, or a value written by an older/different schema).

Common situations: EF Core external-authentication tables edited or truncated by hand; rows written by an older Elsa version before a payload-type change; copy/paste errors seeding the database; migrations that altered the payload shape without re-serializing existing rows.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/ExternalAuthenticationJsonSerializer.cs:12

using System.Text.Json;

namespace Elsa.ExternalAuthentication.Persistence.EFCore;

internal static class ExternalAuthenticationJsonSerializer
{
    private static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web);

    public static string Serialize<T>(T value) => JsonSerializer.Serialize(value, Options);

    public static T Deserialize<T>(string value) => JsonSerializer.Deserialize<T>(value, Options)
        ?? throw new InvalidOperationException($"The persisted external authentication value could not be deserialized as {typeof(T).Name}.");
}

View on GitHub (pinned to fe9217bdfa)