Skip to content

Identity: phone number confirmation flow #174

@antosubash

Description

@antosubash

Summary

Add a phone number confirmation flow that mirrors the existing email confirmation flow. Users can already set a phone number on their profile, but the value is never verified — PhoneNumberConfirmed stays false and there is no way for the system (or other modules) to trust that the number actually belongs to the user.

Why we need this

ASP.NET Identity exposes a full phone confirmation surface (GenerateChangePhoneNumberTokenAsync, ChangePhoneNumberAsync, VerifyChangePhoneNumberTokenAsync, IsPhoneNumberConfirmedAsync, PhoneNumberConfirmed) and we use almost none of it. Without confirmation:

  • We cannot use SMS as a 2FA channel (Identity will refuse to send tokens to an unconfirmed number).
  • We cannot rely on the phone number for account recovery, support contact, or transactional alerts.
  • Compliance frameworks that require a verified second contact channel (SOC 2, some financial regulations) treat unverified phone numbers as worthless.
  • Users who mistype their number have no signal that they did so.

The email flow already exists end-to-end (Pages/Account/Manage/EmailEndpoint.cs, ConfirmEmailEndpoint, ConfirmEmailChangeEndpoint, ResendEmailConfirmationEndpoint). This issue brings phone numbers up to parity.

How the user will use it

End user flow (in Manage/Index or a new Manage/Phone page):

  1. User enters or edits a phone number, presses "Send verification code".
  2. Backend calls UserManager.GenerateChangePhoneNumberTokenAsync(user, phoneNumber) and dispatches the token through an ISmsSender abstraction (start with ConsoleSmsSender for dev, mirror ConsoleEmailSender).
  3. User enters the 6-digit code, backend calls UserManager.ChangePhoneNumberAsync(user, phoneNumber, token) which atomically updates the number and sets PhoneNumberConfirmed = true.
  4. UI shows a green "Verified" badge next to the phone number, similar to the existing email confirmed badge.
  5. If user changes the number later, the badge resets and they must reconfirm.

Admin flow: in the user edit page, show phone number with a "Verified / Unverified" indicator. Admin can clear the confirmation (force re-verification) the same way ForceEmailReverificationAsync works today.

Implementation notes

  • New endpoints in modules/Users/src/SimpleModule.Users/Pages/Account/Manage/:
    • SendPhoneVerificationCodeEndpoint (POST) — generates and sends token.
    • ConfirmPhoneNumberEndpoint (POST) — verifies token, calls ChangePhoneNumberAsync.
    • RemovePhoneNumberEndpoint (POST) — clears number + confirmation flag.
  • New ISmsSender abstraction in SimpleModule.Users.Contracts + ConsoleSmsSender default implementation (logs token to console, like ConsoleEmailSender). Real SMS providers (Twilio, etc.) plug in via DI replacement.
  • Update Manage/Index.tsx (or split into a dedicated Manage/Phone.tsx page) to show verification UI and badge.
  • Admin tab UserSecurityTab should display phone confirmed status and a "Force phone re-verification" button (mirrors ForceEmailReverificationAsync).
  • Remember to register Manage/Phone (if added) in Pages/index.ts per the Pages Registry rule.
  • Tests: integration tests for happy path, wrong code, expired token, changing number invalidates previous confirmation.

Benefits

  • Unblocks SMS-based 2FA as a future feature (the user manager will then accept it).
  • Verified phone is usable for account recovery / support workflows.
  • Closes a meaningful piece of the ASP.NET Identity surface area we already pay the schema cost for.
  • Establishes the ISmsSender abstraction other modules can reuse (notifications, alerts).

Acceptance criteria

  • User can set, verify, and remove a phone number from Manage/.
  • PhoneNumberConfirmed flips to true only after a valid code is entered.
  • Changing the number resets the confirmation flag.
  • Admin can see verification status and force re-verification.
  • ISmsSender abstraction exists with a console default; real provider can be plugged in via DI.
  • Integration tests cover happy path, invalid code, and number change reset.
  • Pages/index.ts registers any new view endpoints; npm run validate-pages passes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions