SMS authentication in a mobile app: where login breaks
Login by phone number looks like the simplest form of authentication, so it is rarely designed separately.
A user expects a one-time code on their phone within seconds and, if it doesn't arrive, closes the app — bringing them back later is more expensive than making initial phone-number registration predictable from the start.
First class of failures — undelivered code. A message can get stuck on the aggregator side, be filtered out by the operator, or get lost when switching from Wi-Fi to mobile network.
If the logic does not distinguish between “sent” and “delivered” statuses, the app shows a false confirmation and does not allow retrying.
Second class — duplicates and request races. A user taps the button twice, the app restarts, the request is sent again — and two different codes arrive for phone number confirmation, only the last of which is valid.
Without idempotent processing and code lifetime tracking, such requests are handled manually in support.
Third class — session loss. Phone-number registration completes successfully, but when changing device or network the access key becomes invalid, and the person again hits the login screen.
Often this is a consequence of too short a session lifetime or its rigid binding to the communication channel.
All three classes of failures are visible on the client, but are fixed at the junction of the app, the authentication service, and the SMS gateway. Below we break down how this is closed — from flow diagram to error handling.
What managed code-based login gives: timelines and reliability
Share of successful logins is determined not by the server side, but by the number of steps the user goes through.
When the code is inserted into the field automatically from the incoming message, there is no need to minimize the app and retype digits manually — on a mass flow this raises the share of completed logins from 65–75% to 90%+, and the time from code request to login fits within 15–25 seconds.
Reliability rests on several things: normalizing the number to a single format before sending, cutting off virtual and corporate ranges where they are not needed, and automatically reading the code from the notification shade.
Repeated code requests are the main source of extra sends and support contacts, so a limit with increasing delay is set per number and per device, and resending becomes a separate scenario rather than a “send again” button.
We hold peak load with a queue: send requests are placed in a buffer, distributed across several delivery routes — our own direct connections to operators and an aggregator such as Twilio as a backup channel.
If one route starts to slow down or return errors, traffic goes to the second without user involvement, and code verification remains idempotent: a repeated confirmation attempt does not create duplicate sessions.
In total, this gives login that does not depend on the operator, peak hour, or connection quality at the moment of registration: the code arrives via the backup channel if the primary is silent, and support requests on “code didn't arrive” leave the top — instead of manual triage, metrics remain for each step and a clear delivery log.
Comparison of login schemes: code by number, password, link
Login scheme is not one screen, but rules for all cases: new device, number change, database leak, access recovery. Below is a comparison of the options that are requested most often: they are compatible, and a combination of two or three usually works in one app.
| Login option | When it fits | Limitations |
|---|---|---|
| Code by number | Mobile app, number already in the profile, registration from scratch | Depends on message delivery; number is lost or transferred; attempt limit needed |
| Login and password | Old accounts, login from someone else's device, web version | Forgotten and reused passwords; complexity rules and reset needed |
| Passwordless login via link in email | Rare logins, return to computer, guest access | Switching between email and app; delivery delay; short link lifetime |
| Code as second factor | Before payment, number change, access to personal data | Extra step on every login if enabled everywhere |
Second factor at login does not replace the main scheme, but builds on top of it. We design access recovery in advance: backup channel, one-time code lifetime, limit on the number of requests.
Otherwise a user with a changed number is left without login, and support gets a stream of manual confirmations.
The schemes are compatible: we keep the password for old accounts, enable passwordless login for new ones, and connect the code selectively. During assessment we look at your audience and scenarios and propose a set that covers them without extra steps.
How we implement code-based login: from audit to acceptance
Code-based login breaks not in the interface, but at the junction of app, gateway, and operator: the code doesn't arrive, arrives twice, is cut off by antifraud.
That is why the steps follow a fixed sequence — a verified route goes to acceptance, not a demo of “entered number — saw field”.
- Login scenario audit. We analyze screens, states, and errors: what we show on timeout, what on an invalid code, where we return after cancellation. We fix requirements — code length, lifetime, number of attempts, behavior when changing the number on a device with two SIM cards.
- Authentication flow diagram. We draw routes: first login, repeat login, device change, access recovery. Separately — fallback options: autofill from a system notification, resend, backup delivery channel.
- Code sending setup. We connect providers, distribute traffic, set limits per number, device, and address, configure templates and rules for switching on non-delivery.
- Testing on staging. We run cancellation, blocking, code expiration, manual entry, and offline operation scenarios, and compare delivery statuses on the app and gateway sides.
- Login load testing. We send a peak registration flow, measure service response time, share of delivered messages, and queue behavior under operator delay. Here we also calibrate limits so that legitimate users do not hit brute-force protection.
- Acceptance and handover. We provide the flow diagram, limit configuration, test numbers, and a pre-release checklist, and show the customer's team incident analysis using logs.
After that, changes to the login scenario follow the same route: diagram update, limit rebuild, test run.
What we hand over: settings, diagrams, procedures
We hand over not an archive with source code, but a kit that the on-call team can use to operate code-based login on their own: without reading code and without contacting us for every limit change. The composition is as follows.
- Code sending configuration: message templates by language, sender signature, provider order, and the rule for switching to backup on non-delivery — failure of one channel does not stop login.
- Login scenario diagram: diagrams of initial registration, repeat login, number change, and access recovery, indicating code request points and behavior when its lifetime expires.
- Access rules and limits: table of roles in the admin panel, request frequency per number and per device, block duration after a series of incorrect entries. Limits live in configuration, not in code.
- Number handling rules: storage format, masking in logs and exports, retention period, and deletion procedure upon user request.
- Support documentation: analysis of typical requests — code didn't arrive, arrived late, number is taken, operator changed — indicating which parameter to check and what is safe to change.
- Incident analysis procedure: levels, delivery metrics in the on-call engineer's responsibility area, escalation procedure, and post-incident report form.
- Access handover: list of accounts and keys with owners, rotation and revocation procedure when an employee leaves.
Such a kit shortens the path from a problem signal to a solution: the on-call engineer changes a parameter in the configuration and sees the result without waiting for a new version build.
Case: reduced the share of failed logins threefold
A delivery service «Пульс» came to us with a task that looked solved: login by phone number with a code from a message. According to app logs, 62% of those who started login reached the main screen, while the remaining 38% abandoned the attempt at the entry step. Support received a login request in almost every seventh install session.
Analysis of the path showed that the problem was not in SMS authentication itself, but in its implementation.
The code was not inserted automatically, when minimizing the app or switching network the step state was lost, and resending occurred on every tap without limits — some users requested 4-6 messages, the last code arrived, and the previously entered one was considered incorrect.
What we changed
We consolidated confirmation into one screen with code autofill and increasing delay between repeat requests.
We added receiving the code from the previous message, preserving step state when returning to the app, a single session loop between launches, and end-to-end telemetry by steps — now it is clear exactly where the user is lost.
The share of successful phone number confirmations grew from 62% to 91%. Time to enter the app decreased from 45 to 9 seconds on an average device. Repeated code requests fell by half, support requests about login — from 14% to 4% of the number of logins.
The same loop was transferred to the customer's other products: metrics hold because this is not a one-time screen fix, but a set of code handling rules plus observability for each step.
Why the SMS code doesn't arrive: diagnosing common failures
“The code doesn't arrive” is almost always not a single cause, but a specific link in the chain: app, server, messaging provider, operator network, device. Until the links are separated by logs and delivery statuses, fixes are guesswork.
| Symptom | Cause | What we do |
|---|---|---|
| Code takes minutes instead of seconds | Code delivery delay: queue at the provider or channel overload | Connect a backup route and switch by status |
| Code doesn't arrive for numbers of one operator | Operator blocks messaging due to complaints or template | Agree on text and signature, distribute traffic across two providers |
| Status “delivered”, no message | Device filter or full inbox | Duplicate with a voice call, suggest where to look |
| One number gets no code, others do | Incorrect phone number or record format | Normalize the number to international format before sending |
| After several attempts sending stopped | Limit on code sending per number and device | Count attempts on the server, keep a pause between them |
Diagnostics rest on the log: for each request we write the send time, provider status, and device receipt confirmation. It is visible where it breaks — with us, at the provider, or in the operator network — and the fix goes to a specific link.
Separately, we close brute force: the code lives for a limited time, the number of entries per session is finite, and resending is not available instantly.
This is also protection against limits — without it, operator restrictions trigger on real people, and authentication fails for everyone.
FAQ: limits, security, support
Before project handover, customers ask the same questions: how login is protected from code brute force, what sending limits exist, what happens when the number changes and logging in from a new device. Below are short answers; we discuss controversial points together with engineers.
How is the code protected from brute force?
The code is one-time and lives for several minutes. The service counts incorrect entries: after several attempts the session is reset, and the number receives a block with increasing delay.
Additionally, the device from which the code was requested is verified — someone else's entry does not pass.
What are the limits on sending messages?
The limit is set per number and per device: several messages per interval, then sending is postponed. Mass number brute force through the login form does not pass, and message spend remains predictable.
What happens when the phone number changes?
The user confirms the new number with a code, and only after that the binding changes — the old one remains working during confirmation. This way access is not lost if the SIM card is lost or the number is re-registered.
Can I log in from multiple devices?
Yes, sessions are bound to the device, not the number: login on a tablet does not end login on a phone. The profile shows a list of active devices with the last login date, and any session can be revoked.
What about support after implementation?
After launch we stay in touch: we monitor the share of delivered messages and login errors, and respond to changes on the SMS gateway side and platform updates. Improvements to login logic go through the same stages — fix, test, release.
Let's discuss the task and come back with a plan and estimate
So that the timeline estimate is based on facts rather than assumptions, before start we need inputs along four axes: existing login scenarios, priority of verification channels, access, and the person responsible for acceptance.
The more specific this set is, the fewer assumptions in the login implementation plan and the more honest the scope of work.
- Scenarios: registration, login, access recovery, number change, login from a new device — what goes first.
- Channel priority: SMS as primary, backup — voice call or messenger message if the code doesn't arrive.
- Access: app staging environment, repository, SMS provider account, monitoring tools if already used.
- Constraints: platforms and minimum supported versions, app store requirements for phone number confirmation.
- Person responsible for acceptance: who approves message texts, blocking rules, and signs off on the result.
- Deadline: the date by which login must work in production, and what can be moved to the second stage.
- Analytics: how many login attempts, what share of failures, whether there are complaints about undelivered codes.
In response we send a login implementation plan: stages, content of each, what is required from you, and what is considered ready.
Along with it — scope of work and timeline estimate by stage, not as a single number: this way secondary items can be moved without shifting the launch date.
Separately we describe login behavior when the code is not delivered and the procedure for rolling back to the previous scenario.
The next step is task analysis: describe the current login scenario, name priority channels and the required deadline. We will come back with a plan, scope of work, and estimate.







