Skip to content

Identity: add resend cooldown + verification attempt throttling for email and phone #199

@antosubash

Description

@antosubash

Summary

Identity verification endpoints currently have no resend cooldowns or brute-force protection. This was raised on PR #198 by Copilot's automated review and the security argument generalizes to the email flow as well, so we're filing it as a single follow-up rather than landing a phone-only mitigation.

Affected endpoints

Email (already merged):

  • POST /Identity/Account/Manage/Email — re-sends change-email confirmation link; no cooldown.
  • POST /Identity/Account/ResendEmailConfirmation — anonymous resend; no cooldown.

Phone (added in #198 / #174):

  • POST /Identity/Account/Manage/SendPhoneVerificationCode — generates token, dispatches via ISmsSender. No cooldown. Risk: signed-in user can pump SMS to arbitrary numbers, driving provider costs.
  • POST /Identity/Account/Manage/ConfirmPhoneNumber — accepts 6-digit code. No failed-attempt tracking. Risk: signed-in attacker (or stolen session) can brute-force ChangePhoneNumberAsync until a code lands.

What we want

  1. Resend cooldown — per-user, per-channel (email or phone), e.g. 60 seconds between sends. Probably backed by IDistributedCache so it works across processes.
  2. Attempt cap on code submission — for both email-confirmation links (less critical, signed token) and phone 6-digit codes (critical, narrow search space). Lock further attempts for N minutes after K failures.
  3. Auditing — emit an AuditLogs event on resend and on failed verification so abuse is visible.

Notes

  • Token TTL is already short (default 1h for DataProtectionTokenProvider), but that does nothing against a fast brute-forcer of 6 digits.
  • We have SimpleModule.RateLimiting available — route-level limits would catch the most blatant cases for free, but won't stop a single authenticated session pacing itself under the threshold. The real fix is per-user/per-channel counters.
  • Out of scope for Identity: phone number confirmation flow #174 (phone parity); shipping that PR first and opening this so neither flow gets forgotten.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions