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
- Ensure request.TenantId matches the target user's TenantId exactly (including case and null vs empty).
- If multi-tenancy was recently enabled, migrate legacy users to the correct TenantId instead of linking cross-tenant.
- Fix tenant resolution (tenant header/route resolvers) so the provisioning request targets the tenant that owns the user.
- 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 enabling multi-tenancy, run a migration that sets TenantId on all existing users.
- Test tenant resolution configuration so requests carry the tenant that actually owns the user.
- Compare tenant IDs with StringComparison.Ordinal consistently (null vs empty matters).
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
- The requested Elsa user does not exist.
- A user creation proposal is required for an unlinked…
- A unique Elsa user name could not be reserved for the…
- Cannot overwrite an AI conversation that belongs to another…
- Cannot overwrite an AI conversation that belongs to another…
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)