{"record":{"id":"ff5560d4ee80b5a2","repo":"elsa-workflows/elsa-core","slug":"a-user-creation-proposal-is-required-for-an-unlinked","errorCode":null,"errorMessage":"A user creation proposal is required for an unlinked external identity.","messagePattern":"A user creation proposal is required for an unlinked external identity\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication/Services/ExternalIdentityUserProvisioningService.cs","lineNumber":40,"sourceCode":"    /// Resolves an explicitly selected user or creates a credential-less user from the supplied proposal.\n    /// </summary>\n    public async ValueTask<(User User, bool WasCreated)> ResolveAsync(\n        ProvisioningRequest request,\n        Func<string, bool>? tryReserveUserName = null,\n        CancellationToken cancellationToken = default)\n    {\n        if (!string.IsNullOrWhiteSpace(request.ExistingUserId))\n        {\n            var existingUser = await userProvider.FindAsync(new()\n                                   { Id = request.ExistingUserId }, cancellationToken)\n                ?? throw new InvalidOperationException(\"The requested Elsa user does not exist.\");\n            if (!string.Equals(existingUser.TenantId, request.TenantId, StringComparison.Ordinal))\n                throw new InvalidOperationException(\"The requested Elsa user is outside the target tenant.\");\n\n            return (existingUser, false);\n        }\n\n        var proposal = request.Proposal ?? throw new InvalidOperationException(\"A user creation proposal is required for an unlinked external identity.\");\n        var roleIds = await ResolveRoleIdsAsync(proposal.DefaultRoleIds, cancellationToken);\n        var prefix = NormalizeUserNamePrefix(proposal.UserNamePrefix);\n        for (var attempt = 0; attempt < MaximumUserNameAttempts; attempt++)\n        {\n            var name = $\"{prefix}-{identityGenerator.GenerateId()}\";\n            if (tryReserveUserName is not null && !tryReserveUserName(name))\n                continue;\n            if (await userProvider.FindAsync(new()\n                    { Name = name }, cancellationToken) is not null)\n                continue;\n\n            var user = new User\n            {\n                Id = identityGenerator.GenerateId(),\n                Name = name,\n                TenantId = request.TenantId,\n                HashedPassword = null,\n                HashedPasswordSalt = null,","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication/Services/ExternalIdentityUserProvisioningService.cs#L22-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Supply a valid ProvisioningRequest.Proposal with DefaultRoleIds and UserNamePrefix when ExistingUserId is not set.","If the identity should link to an existing user, set ExistingUserId instead of leaving both fields empty.","Check the external-authentication configuration (default roles, user name prefix) so the provisioning pipeline can build a proposal."],"exampleFix":"// before\nvar request = new ProvisioningRequest { TenantId = tenantId }; // no Proposal\n// after\nvar request = new ProvisioningRequest\n{\n    TenantId = tenantId,\n    Proposal = new UserCreationProposal { DefaultRoleIds = [\"admin\"], UserNamePrefix = \"oidc\" }\n};","handlingStrategy":"validation","validationCode":"if (string.IsNullOrWhiteSpace(request.ExistingUserId) && request.Proposal is null)\n    throw new ArgumentException(\"Either ExistingUserId or Proposal must be provided.\", nameof(request));","typeGuard":"bool IsProvisionable(ProvisioningRequest r) => !string.IsNullOrWhiteSpace(r.ExistingUserId) || r.Proposal is not null;","tryCatchPattern":"try\n{\n    var (user, created) = await provisioning.ResolveAsync(request, ct: ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message == \"A user creation proposal is required for an unlinked external identity.\")\n{\n    request.Proposal = proposalFromConfiguration; // load defaults from options\n    var (user, created) = await provisioning.ResolveAsync(request, ct: ct);\n}","preventionTips":["Build ProvisioningRequest through a factory that enforces the ExistingUserId-or-Proposal invariant.","Bind default proposal values (roles, prefix) from configuration so they are never null.","Add a unit test asserting IsProvisionable for all request construction paths."],"tags":["identity","missing-argument","provisioning","external-authentication"],"backgroundTag":"missing-required-argument","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}