fullstackhero/dotnet-starter-kit · error · CustomException

An error occurred while confirming phone number.

Error message

An error occurred while confirming phone number.

What it means

A deliberately vague CustomException thrown by ConfirmPhoneNumberAsync when no unconfirmed-phone user is found for the given userId. The query filters u.Id == userId && !u.PhoneNumberConfirmed, so it fires both when the user doesn't exist and when the phone number is already confirmed. The generic message avoids leaking account existence.

Solutions

  1. Check whether the phone is already confirmed and show 'already confirmed' instead of retrying
  2. Verify the userId is correct and belongs to the current tenant
  3. Request a fresh phone-confirmation code if the previous flow already completed
  4. Ensure the code token hasn't expired and resend the SMS if needed

Example fix

// before
var ok = await service.ConfirmPhoneNumberAsync(userId, code, ct);
// after
var user = await userManager.Users.FirstOrDefaultAsync(u => u.Id == userId, ct);
if (user?.PhoneNumberConfirmed == true) return Results.Ok("Phone already confirmed.");
if (user is null) return Results.NotFound();
var ok = await service.ConfirmPhoneNumberAsync(userId, code, ct);
Defensive patterns

Strategy: validation

Validate before calling

var user = await userManager.Users.FirstOrDefaultAsync(u => u.Id == userId); var eligible = user is not null && !user.PhoneNumberConfirmed;

Type guard

if (user is null || user.PhoneNumberConfirmed) return Results.BadRequest("User not found or phone already confirmed.");

Try / catch

catch (CustomException ex) { return Results.BadRequest(new { error = "phone-confirm-failed", hint = "Check userId, tenant, and whether the phone is already confirmed." }); }

Prevention

When it happens

Trigger: ConfirmPhoneNumberAsync called with an unknown userId; the user's phone is already confirmed (filtered out by !PhoneNumberConfirmed); wrong tenant so the user isn't visible.

Common situations: User submits the phone code twice; code email/SMS link reused after success; userId from a different tenant; test fixture with unseeded users.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/ddf8c9b59dcd778a. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/UserRegistrationService.cs:147

        {
            throw new CustomException(string.Format(
                CultureInfo.InvariantCulture,
                "The email for {0} is already confirmed.",
                user.Email));
        }

        await SendConfirmationEmailAsync(user, origin, cancellationToken);
    }

    public async Task<string> ConfirmPhoneNumberAsync(string userId, string code, CancellationToken cancellationToken = default)
    {
        EnsureValidTenant();

        var user = await userManager.Users
            .Where(u => u.Id == userId && !u.PhoneNumberConfirmed)
            .FirstOrDefaultAsync(cancellationToken);

        _ = user ?? throw new CustomException("An error occurred while confirming phone number.");

        code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
        var result = await userManager.ChangePhoneNumberAsync(user, user.PhoneNumber!, code);

        return result.Succeeded
            ? string.Format(CultureInfo.InvariantCulture, "Phone number {0} confirmed successfully.", user.PhoneNumber)
            : throw new CustomException(string.Format(CultureInfo.InvariantCulture, "An error occurred while confirming phone number {0}", user.PhoneNumber));
    }

    private void EnsureValidTenant()
    {
        if (string.IsNullOrWhiteSpace(multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id))
        {
            throw new UnauthorizedException("invalid tenant");
        }
    }

    private static string ExtractEmailFromPrincipal(ClaimsPrincipal principal)

View on GitHub (pinned to 3f2959e683)