{"record":{"id":"ddf8c9b59dcd778a","repo":"fullstackhero/dotnet-starter-kit","slug":"an-error-occurred-while-confirming-phone-number","errorCode":null,"errorMessage":"An error occurred while confirming phone number.","messagePattern":"An error occurred while confirming phone number\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Services/UserRegistrationService.cs","lineNumber":147,"sourceCode":"        {\n            throw new CustomException(string.Format(\n                CultureInfo.InvariantCulture,\n                \"The email for {0} is already confirmed.\",\n                user.Email));\n        }\n\n        await SendConfirmationEmailAsync(user, origin, cancellationToken);\n    }\n\n    public async Task<string> ConfirmPhoneNumberAsync(string userId, string code, CancellationToken cancellationToken = default)\n    {\n        EnsureValidTenant();\n\n        var user = await userManager.Users\n            .Where(u => u.Id == userId && !u.PhoneNumberConfirmed)\n            .FirstOrDefaultAsync(cancellationToken);\n\n        _ = user ?? throw new CustomException(\"An error occurred while confirming phone number.\");\n\n        code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));\n        var result = await userManager.ChangePhoneNumberAsync(user, user.PhoneNumber!, code);\n\n        return result.Succeeded\n            ? string.Format(CultureInfo.InvariantCulture, \"Phone number {0} confirmed successfully.\", user.PhoneNumber)\n            : throw new CustomException(string.Format(CultureInfo.InvariantCulture, \"An error occurred while confirming phone number {0}\", user.PhoneNumber));\n    }\n\n    private void EnsureValidTenant()\n    {\n        if (string.IsNullOrWhiteSpace(multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id))\n        {\n            throw new UnauthorizedException(\"invalid tenant\");\n        }\n    }\n\n    private static string ExtractEmailFromPrincipal(ClaimsPrincipal principal)","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Services/UserRegistrationService.cs#L129-L165","documentation":"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.","triggerScenarios":"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.","commonSituations":"User submits the phone code twice; code email/SMS link reused after success; userId from a different tenant; test fixture with unseeded users.","solutions":["Check whether the phone is already confirmed and show 'already confirmed' instead of retrying","Verify the userId is correct and belongs to the current tenant","Request a fresh phone-confirmation code if the previous flow already completed","Ensure the code token hasn't expired and resend the SMS if needed"],"exampleFix":"// before\nvar ok = await service.ConfirmPhoneNumberAsync(userId, code, ct);\n// after\nvar user = await userManager.Users.FirstOrDefaultAsync(u => u.Id == userId, ct);\nif (user?.PhoneNumberConfirmed == true) return Results.Ok(\"Phone already confirmed.\");\nif (user is null) return Results.NotFound();\nvar ok = await service.ConfirmPhoneNumberAsync(userId, code, ct);","handlingStrategy":"validation","validationCode":"var user = await userManager.Users.FirstOrDefaultAsync(u => u.Id == userId); var eligible = user is not null && !user.PhoneNumberConfirmed;","typeGuard":"if (user is null || user.PhoneNumberConfirmed) return Results.BadRequest(\"User not found or phone already confirmed.\");","tryCatchPattern":"catch (CustomException ex) { return Results.BadRequest(new { error = \"phone-confirm-failed\", hint = \"Check userId, tenant, and whether the phone is already confirmed.\" }); }","preventionTips":["Remember this error conflates 'not found' and 'already confirmed' — check both client-side","Verify tenant context before calling","Only submit the code once; regenerate for retries"],"tags":["identity","phone-confirmation","ambiguous-error"],"backgroundTag":"record-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}