elsa-workflows/elsa-core · error · InvalidOperationException

A user creation proposal is required for an unlinked…

Error message

A user creation proposal is required for an unlinked external identity.

What it means

ResolveAsync throws this when request.ExistingUserId is empty (unlinked external identity, meaning a new user should be created) but request.Proposal is null. The proposal carries the default role IDs and user-name prefix needed to create the new credential-less Elsa user, so without it the service cannot proceed.

Solutions

  1. Supply a valid ProvisioningRequest.Proposal with DefaultRoleIds and UserNamePrefix when ExistingUserId is not set.
  2. If the identity should link to an existing user, set ExistingUserId instead of leaving both fields empty.
  3. Check the external-authentication configuration (default roles, user name prefix) so the provisioning pipeline can build a proposal.

Example fix

// before
var request = new ProvisioningRequest { TenantId = tenantId }; // no Proposal
// after
var request = new ProvisioningRequest
{
    TenantId = tenantId,
    Proposal = new UserCreationProposal { DefaultRoleIds = ["admin"], UserNamePrefix = "oidc" }
};
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(request.ExistingUserId) && request.Proposal is null)
    throw new ArgumentException("Either ExistingUserId or Proposal must be provided.", nameof(request));

Type guard

bool IsProvisionable(ProvisioningRequest r) => !string.IsNullOrWhiteSpace(r.ExistingUserId) || r.Proposal is not null;

Try / catch

try
{
    var (user, created) = await provisioning.ResolveAsync(request, ct: ct);
}
catch (InvalidOperationException ex) when (ex.Message == "A user creation proposal is required for an unlinked external identity.")
{
    request.Proposal = proposalFromConfiguration; // load defaults from options
    var (user, created) = await provisioning.ResolveAsync(request, ct: ct);
}

Prevention

When it happens

Trigger: Calling ResolveAsync with a ProvisioningRequest that has neither ExistingUserId nor Proposal set — e.g. constructing the request manually and forgetting to supply the proposal, or an external identity provider flow that never configured default user creation options.

Common situations: Custom login integrations that build ProvisioningRequest by hand; upgrading external authentication packages where a previously optional proposal field was not populated; configuration sections for default roles/prefix omitted so the proposal is left null.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication/Services/ExternalIdentityUserProvisioningService.cs:40

    /// Resolves an explicitly selected user or creates a credential-less user from the supplied proposal.
    /// </summary>
    public async ValueTask<(User User, bool WasCreated)> ResolveAsync(
        ProvisioningRequest request,
        Func<string, bool>? tryReserveUserName = null,
        CancellationToken cancellationToken = default)
    {
        if (!string.IsNullOrWhiteSpace(request.ExistingUserId))
        {
            var existingUser = await userProvider.FindAsync(new()
                                   { Id = request.ExistingUserId }, cancellationToken)
                ?? throw new InvalidOperationException("The requested Elsa user does not exist.");
            if (!string.Equals(existingUser.TenantId, request.TenantId, StringComparison.Ordinal))
                throw new InvalidOperationException("The requested Elsa user is outside the target tenant.");

            return (existingUser, false);
        }

        var proposal = request.Proposal ?? throw new InvalidOperationException("A user creation proposal is required for an unlinked external identity.");
        var roleIds = await ResolveRoleIdsAsync(proposal.DefaultRoleIds, cancellationToken);
        var prefix = NormalizeUserNamePrefix(proposal.UserNamePrefix);
        for (var attempt = 0; attempt < MaximumUserNameAttempts; attempt++)
        {
            var name = $"{prefix}-{identityGenerator.GenerateId()}";
            if (tryReserveUserName is not null && !tryReserveUserName(name))
                continue;
            if (await userProvider.FindAsync(new()
                    { Name = name }, cancellationToken) is not null)
                continue;

            var user = new User
            {
                Id = identityGenerator.GenerateId(),
                Name = name,
                TenantId = request.TenantId,
                HashedPassword = null,
                HashedPasswordSalt = null,

View on GitHub (pinned to fe9217bdfa)