fullstackhero/dotnet-starter-kit · error · CustomException

An error occurred while confirming phone number

Error message

An error occurred while confirming phone number {0}

What it means

Thrown as a CustomException when userManager.ChangePhoneNumberAsync returns a failed IdentityResult — most commonly because the supplied code is invalid, expired, or malformed after Base64Url decoding. The message includes the phone number for context. It means the code could not be verified against the generated phone-confirmation token.

Solutions

  1. Have the user re-enter the code carefully, then request a fresh code if it fails again
  2. Verify the code is passed unmodified (no extra URL encoding/trimming) to the API
  3. If the security stamp changed (password reset, 2FA change), regenerate the phone confirmation token
  4. Check clock skew and token lifetime settings if codes expire prematurely

Example fix

// before
var msg = await service.ConfirmPhoneNumberAsync(userId, rawCode, ct);
// after
try
{
    var msg = await service.ConfirmPhoneNumberAsync(userId, Uri.UnescapeDataString(rawCode.Trim()), ct);
}
catch (CustomException)
{
    await service.SendPhoneCodeAsync(userId); // fresh code
    throw new InvalidCodeError("Code invalid or expired — a new code has been sent.");
}
Defensive patterns

Strategy: validation

Validate before calling

string normalized = code?.Trim(); if (string.IsNullOrWhiteSpace(normalized) || normalized.Any(char.IsWhiteSpace)) throw new ArgumentException("Code must be a non-empty Base64Url string");

Type guard

bool IsValidBase64Url(string s) => !string.IsNullOrWhiteSpace(s) && s.All(c => char.IsAsciiLetterOrDigit(c) || c is '-' or '_');

Try / catch

catch (CustomException ex) { await resendNewCodeAsync(userId); return Results.BadRequest("Code invalid or expired — a new code was sent."); }

Prevention

When it happens

Trigger: Submitting an incorrect SMS code; a code that expired (security-stamp or token lifetime changed); reusing an already-consumed code; code string mangled in transit (padding/url-encoding) so it decodes to the wrong bytes.

Common situations: User types the code wrong; SMS delayed and token expires; two confirmation attempts where the first consumed the token; proxies altering the Base64Url token in query strings.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

        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)
    {
        return principal.FindFirstValue(ClaimTypes.Email)
            ?? principal.FindFirstValue("email")
            ?? throw new CustomException("Email claim is required for external authentication.");
    }

    private async Task<FshUser> CreateUserFromPrincipalAsync(ClaimsPrincipal principal, string email)

View on GitHub (pinned to 3f2959e683)