Build1 distinct publisher2 min readPublished
A dev.to writeup on Node passwordless phone login puts resend cooldowns, attempt caps and lockout in the database. The failure it names is two workers reading the same eligible challenge.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
On-call in that incident saw the sends arrive for one phone number, from several IP addresses, on one device [c2b]. That distribution decides which key the cooldown has to be stored against. The backend is meant to check an increasing cooldown and daily caps across phone, IP and device before it allows a resend [5], and of those three keys, phone and device are the ones that hold still under the burst while the address does not [1]. A limiter keyed on IP reads that traffic as a crowd of well-behaved strangers, each making its first request.
The second leak is counting rather than keying. If admission is not committed in the same transaction as the cap check, two Node.js workers can read the same eligible challenge, both conclude the cooldown has ended, and both spend send capacity before either counter becomes visible to the other [10]. It does not stop at two: the stored next-send time is only a floor per worker, so W workers racing the same challenge admit up to W sends inside one cooldown window [2]. The correction the source asks for is ordering, not a longer cooldown. Apply the phone, IP and device caps, commit admission, and only then dispatch the message [8].
Suppression sits on the same side of that commit, because repeatedly attempting numbers that are already blocked generates traffic without improving delivery [11]. That is the cheapest rejection available and it is the one most often skipped, since the provider will accept the request and charge for it.
None of this needs much stored state. 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 are enough to enforce every transition, and none of them may come from the client [7]. The verify path does the matching atomic work: increment the failure counter, move to locked at the configured maximum, and consume the challenge on success so it cannot authenticate a second session [6].
The diagnostic habit that follows is the useful part. When sends pile up, the question is not whether the SMS API returned a success, but whether the authentication state machine admitted work that policy should have refused before any message left the building [3]. A client-side countdown cannot answer that question, because it was never asked.
Ranked by verification strength, evidence, and original report placement.
A passwordless phone login is described as 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.
A client-provided timer must not become a security boundary: the visible countdown is UX, the stored deadline is policy.
The useful question is not whether the SMS API responded, but whether the authentication state machine allowed work that policy should have rejected before a send occurred.
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, allowed 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.
The persisted 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, with the client never treated as source of truth for any of them.
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Coherent single-source design argument, no artifacts
All material comes from one self-published dev.to post by one author. The mechanism reasoning is internally consistent and specific — a named state machine, an enumerated persisted record, an explicit cap-then-admit transaction ordering, and a concrete concurrency failure — which is why evidence is not near zero. But nothing is demonstrated: no code, schema, lock strategy, load test, provider logs, or incident timeline accompanies the narrated burst, no second publisher corroborates any element, and the derived worker-multiplication and IP-keying points are logical consequences of the author's own framing rather than observed measurements.
No adoption signal in cluster
The supplied source contains no release, deployment, benchmark, pricing or usage disclosure for the described pattern: no named team, storefront, repository, or production system is reported as running it, and the only quantities present (295 routes, 20 modules) describe a vendor's advertised capability surface rather than observed usage. Inferring adoption from a design essay would be guessing.
Mildly overstated diagnostic certainty, heavily self-hedged
The framing is more certain than the evidence base: an unverified narrated incident and a non-reproduced race are presented as 'the concrete failure to eliminate', and admission pressure is asserted to be the earlier signal without any comparative detection data. The gap stays small because the author actively suppresses the usual inflation — refusing universal cooldown and attempt limits, refusing to name an SLO percentage before observing carrier mix, and conceding that neither provider-managed nor application-managed templates remove the backend's policy obligation. The residual positive tilt also reflects a promotional single-key, single-bill vendor mention riding on a technical argument with zero adoption evidence.
Self-published post with an embedded vendor pitch
The piece is authored on a self-publishing developer platform with no editorial gate and closes with a promotional passage for Infrai — one key across 295 routes and 20 modules, one bill for the whole capability surface, one credential rotation and one billing reconciliation surface for the login-and-receipt path — plus a build-versus-delegate framing of provider-managed OTP products. No relationship between author and vendor is disclosed. The score is mid-range rather than high because the promotional layer sits at the end, is explicitly limited ('it does not remove the need to own authentication policy'), and does not distort the technical prescriptions that make up the bulk of the text.
Low-moderate: clear reading, thin substantiation
Confidence in what the source says is high — the text is explicit and the ledger consolidates cleanly. Confidence in the story as intelligence is limited by structural facts: one publisher, one author, zero corroboration, zero adoption signal, an unverified motivating incident, and a commercial mention with no disclosure. The engineering content is standard and checkable enough that a reader can evaluate it independently, which keeps this above the floor.
build
A Timed-Out Reset SMS Is Not A Failed One, And Your Retry Code Probably Disagrees1 distinct publisher
build
Your JWT Login Probably Has Exactly One Kill Switch: Log Everyone Out1 distinct publisher
build
Strapi will send that webhook twice, so your receiver owns the idempotency key1 distinct publisher
build
A unique index is not a duplicate check: the race inside a webhook idempotency middleware1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 24, 2026