Node.js Passwordless Phone Login: SMS OTP Resend Cooldowns and Attempt Caps

Node.js Passwordless Phone Login: SMS OTP Resend Cooldowns and Attempt Caps

Node.js 无密码手机登录:短信验证码重发冷却与尝试次数限制

Short answer: a passwordless phone login is a sound fit for a gaming storefront, provided the Express/Node.js backend owns the SMS OTP resend cooldown, maximum verification attempts, and anti-abuse counters rather than accepting any of those decisions from the browser or game client. 简短回答:对于游戏商店而言,无密码手机登录是一个合理的方案,前提是 Express/Node.js 后端必须掌控短信验证码(OTP)的重发冷却时间、最大验证尝试次数以及防滥用计数器,而不是从浏览器或游戏客户端接收这些决策。

The page arrives after a payment has settled: the player can see a completed order, but the session that should expose the receipt is stuck behind repeated code requests. On-call sees a burst of sends for one phone, several IP addresses, and one device. The useful question isn’t “did the SMS API respond?” It is whether the authentication state machine allowed work that policy should have rejected before a send occurred. That distinction sets the design. 页面在支付完成后加载:玩家可以看到已完成的订单,但本应显示收据的会话却卡在了重复的验证码请求中。值班人员会看到针对同一个手机号、多个 IP 地址和同一台设备的短信发送激增。此时有意义的问题不是“短信 API 是否响应了?”,而是身份验证状态机是否允许了本应在发送前就被策略拒绝的操作。这种区别决定了系统设计。

Express should expose explicit send-code, verify-code, resend-code, and lockout states, persist only the minimum state needed to enforce them, and make the database authoritative for expiry and counters. Don’t let a client-provided timer become a security boundary. The visible countdown is UX; the stored deadline is policy. Express 应明确暴露发送验证码、验证验证码、重发验证码和锁定状态,仅持久化执行这些操作所需的最小状态,并以数据库作为过期时间和计数器的权威来源。不要让客户端提供的计时器成为安全边界。可见的倒计时是用户体验(UX);存储的截止时间才是策略。

How does an Express Node.js SMS OTP resend flow enforce cooldowns? Model the flow before choosing a provider. A code request begins in ready, moves to code_sent, and can end in verified, expired, or locked. A resend is a transition from code_sent to a fresh code_sent, but only after the backend checks its increasing cooldown and daily caps across phone, IP, and device. A verification failure increments a server-side attempt counter; reaching the configured maximum moves the record to locked. A successful verification consumes the challenge so it cannot authenticate a second session. Express Node.js 短信验证码重发流程如何执行冷却时间?在选择服务商之前,先对流程进行建模。验证码请求从“就绪(ready)”开始,进入“已发送(code_sent)”,最终可能变为“已验证(verified)”、“已过期(expired)”或“已锁定(locked)”。重发是从“已发送”到新的“已发送”的状态转换,但前提是后端必须检查针对手机号、IP 和设备的递增冷却时间和每日上限。验证失败会增加服务器端的尝试计数器;达到配置的最大值后,记录将变为“已锁定”。验证成功会消耗掉该挑战(challenge),使其无法用于第二次会话认证。

Keep the policy values configurable. I’m not sure there is a defensible universal cooldown or attempt limit for every game: player geography, carrier delivery time, account value, and attack traffic change the answer. What is defensible is capacity planning from the allowed state transitions. If the service admits one resend where it should admit none, that becomes real SMS load; if it rejects one legitimate resend too early, it becomes login abandonment and, after payment, a support contact about the missing receipt. 保持策略值可配置。我不确定是否存在适用于所有游戏的通用冷却时间或尝试限制:玩家地理位置、运营商送达时间、账户价值和攻击流量都会改变答案。真正站得住脚的是基于允许的状态转换进行容量规划。如果服务在不该允许重发时允许了一次,就会产生实际的短信负载;如果过早拒绝了一次合法的重发,就会导致登录放弃,并在支付后引发关于收据缺失的客服工单。

The state can stay small: an opaque challenge ID, normalized phone identity, expiry, next-send time, send count, failed-attempt count, lockout state, and server-derived references for IP and device policy. Never treat the client as the source of truth for any of them. Check suppression before a send as well, because repeatedly attempting blocked numbers creates traffic without improving delivery. No magic here. The transaction must apply the phone, IP, and device caps, then commit admission before dispatching the message. The verification path performs the corresponding atomic attempt increment and lockout transition. 状态可以保持精简:一个不透明的挑战 ID、规范化的手机身份、过期时间、下次发送时间、发送计数、失败尝试计数、锁定状态,以及从服务器端获取的 IP 和设备策略引用。永远不要将客户端视为这些信息的真实来源。在发送前也要检查抑制列表,因为反复尝试被屏蔽的号码只会产生流量而无法提高送达率。这里没有魔法。事务必须在发送消息前应用手机号、IP 和设备上限,并提交准入记录。验证路径执行相应的原子尝试递增和锁定状态转换。

The browser receives a stable application error such as otp_cooldown or otp_locked, not raw counters that invite probing. Keep those operations together even when the surrounding HTTP application is Express; otherwise two Node.js workers can read the same eligible challenge, both decide that the cooldown has ended, and both spend send capacity before either counter becomes visible to the other. That race is the concrete failure to eliminate, not a prettier countdown component. 浏览器接收到的是稳定的应用错误(如 otp_cooldownotp_locked),而不是会诱导探测的原始计数器。即使外围 HTTP 应用是 Express,也要将这些操作保持在一起;否则,两个 Node.js 工作进程可能会读取同一个符合条件的挑战,同时判定冷却时间已结束,并在计数器更新前消耗掉发送配额。这种竞态条件才是需要消除的具体故障,而不是一个更漂亮的倒计时组件。

Reliability starts with resend admission, not delivery. An alert on failed logins is late. By then, the service has already accepted sends, users are already waiting, and the receipt workflow is caught behind authentication. The earlier signal is admission pressure: the ratio of resend requests rejected by cooldown, cap, suppression, or lockout, partitioned carefully enough to distinguish one abusive key from a broad carrier-delay pattern. 可靠性始于重发准入,而非送达。针对登录失败的警报已经太晚了。到那时,服务已经接受了发送请求,用户已经在等待,而收据工作流也被卡在身份验证之后。更早的信号是准入压力:即被冷却时间、上限、抑制或锁定拒绝的重发请求比例,并进行细致划分,以区分单一的滥用密钥与广泛的运营商延迟模式。

Instrument state transitions, not message contents. Count requests entering send, resend, verify, and lockout; count policy rejections by reason; observe challenge age at successful verification; and record provider dispatch outcomes without putting phone numbers or OTP values into metric labels. High-cardinality identity data belongs in protected logs or an abuse data store, not in the metrics backend. This is both an operational constraint and a capacity constraint — an unbounded phone label can hurt the monitoring system during the same burst it is supposed to explain. 监控状态转换,而不是消息内容。统计进入发送、重发、验证和锁定状态的请求;按原因统计策略拒绝次数;观察验证成功时的挑战时长;记录服务商的发送结果,但不要将手机号或 OTP 值放入指标标签中。高基数的身份数据应存放在受保护的日志或滥用数据存储中,而不是指标后端。这既是操作约束也是容量约束——无限制的手机号标签可能会在它本应解释的流量激增期间,反而拖垮监控系统。

The SLO should describe the user outcome you control. For example, measure the share of eligible login challenges that the backend admits and that reach verified state within the configured expiry window, while separating policy-denied traffic from system failure. The exact objective needs production baselines; inventing a percentage before observing carrier mix would make the page look precise and behave badly. Track receipt access after settlement separately, since a healthy OTP send rate does not prove that a buyer can retrieve an order receipt. SLO(服务水平目标)应描述你所能控制的用户结果。例如,衡量后端准入且在配置的过期窗口内达到验证状态的合格登录挑战比例,同时将策略拒绝的流量与系统故障区分开来。确切的目标需要生产环境的基准数据;在观察运营商组合之前就凭空设定百分比,只会让页面看起来精确但表现糟糕。支付后的收据访问应单独跟踪,因为健康的 OTP 发送率并不能证明买家一定能获取订单收据。

One instrumentation change matters most: emit a single transition event after the database decision, with challenge ID, prior state, next state, policy result, and coarse risk dimensions. It lets an operator reconstruct why a resend was denied without trusting the client’s story, and it makes duplicate admissions visible. 最重要的一项监控变更:在数据库决策后触发一个单一的转换事件,包含挑战 ID、前置状态、后置状态、策略结果和粗略的风险维度。这让运维人员无需信任客户端的说法就能重构重发被拒绝的原因,并使重复准入变得可见。

A staged rollout protects template ownership. For this gaming flow, template ownership is more consequential than the transport call. Login copy, locale, code placement, expiry wording, and the post-payment receipt path all have product and compliance owners. A provider-managed OTP product reduces auth machinery, while an application-managed template keeps wording and fallback decisions in the platform team’s hands. Neither choice removes the backend’s obligation to enforce resend and attempt policy. 分阶段发布可以保护模板所有权。对于这种游戏流程,模板所有权比传输调用更重要。登录文案、语言环境、验证码位置、过期措辞以及支付后的收据路径都有产品和合规负责人。服务商管理的 OTP 产品减少了认证机制的复杂性,而应用管理的模板则将措辞和回退决策权保留在平台团队手中。无论哪种选择,都不能免除后端执行重发和尝试策略的义务。

OptionWhere it fitsTemplate and operating trade-off
选项适用场景模板与运营权衡
Twilio VerifyTeams evaluating a managed verification productValidate its template controls and regional behavior against the game’s ownership requirements; SMS segmentation still affects message construction.
Twilio Verify正在评估托管验证产品的团队根据游戏的所有权要求验证其模板控制和区域行为;短信分段仍会影响消息构建。
Firebase AuthenticationGames already(Content truncated)
Firebase Authentication已经在使用的游戏(内容截断)