elsa-workflows/elsa-core · error · InvalidOperationException

The requested Elsa user is outside the target tenant.

Error message

The requested Elsa user is outside the target tenant.

What it means

ResolveAsync throws this when the resolved existing user's TenantId does not exactly (ordinal, case-sensitive) match request.TenantId. Elsa is multi-tenant: an external identity may only be linked to a user inside the tenant the request targets, so cross-tenant references are rejected rather than silently re-linked.

Solutions

  1. Ensure request.TenantId matches the target user's TenantId exactly (including case and null vs empty).
  2. If multi-tenancy was recently enabled, migrate legacy users to the correct TenantId instead of linking cross-tenant.
  3. Fix tenant resolution (tenant header/route resolvers) so the provisioning request targets the tenant that owns the user.
  4. Re-create the external identity link within the correct tenant so a new user is provisioned there.

Example fix

// before
var request = new ProvisioningRequest { ExistingUserId = userId, TenantId = currentTenantId };
// after: verify tenant match first
var user = await userProvider.FindAsync(new() { Id = userId }, ct);
if (user is null || !string.Equals(user.TenantId, currentTenantId, StringComparison.Ordinal))
    request.ExistingUserId = null; // let the proposal path create a user in the right tenant
Defensive patterns

Strategy: validation

Validate before calling

var user = await userProvider.FindAsync(new() { Id = request.ExistingUserId }, ct);
if (user is not null && !string.Equals(user.TenantId, request.TenantId, StringComparison.Ordinal))
    request.ExistingUserId = null; // cross-tenant link: re-provision in the target tenant

Type guard

bool IsSameTenant(User? user, string? tenantId) => user is not null && string.Equals(user.TenantId, tenantId, StringComparison.Ordinal);

Try / catch

try
{
    var (user, created) = await provisioning.ResolveAsync(request, ct: ct);
}
catch (InvalidOperationException ex) when (ex.Message == "The requested Elsa user is outside the target tenant.")
{
    logger.LogWarning("Cross-tenant identity link for {UserId}; re-provisioning in {TenantId}", request.ExistingUserId, request.TenantId);
    request.ExistingUserId = null;
    var (user, created) = await provisioning.ResolveAsync(request, ct: ct);
}

Prevention

When it happens

Trigger: Calling ResolveAsync with ExistingUserId pointing at a valid user that belongs to a different tenant than request.TenantId — e.g. after enabling multi-tenancy on an app that previously had single-tenant users with null/different TenantId, or an external identity link copied across tenant migrations.

Common situations: Enabling multi-tenancy after upgrade so existing users have TenantId null while the request carries a concrete tenant ID; configuring the wrong tenant resolver so requests claim the wrong tenant; moving identity links between tenants during data migration.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

    IIdentityGenerator identityGenerator)
{
    private const int MaximumUserNameAttempts = 10;

    /// <summary>
    /// 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
            {

View on GitHub (pinned to fe9217bdfa)