Kitten Blog

No, this is not a secure approach. In fact, it introduces serious security vulnerabilities and unnecessary roundtrips.
Here is an honest breakdown of why this flow is vulnerable, followed by the recommended, industry-standard way to structure these APIs.
The Flaws in Your Proposed Flow
1. Critical Security Hole: Account Hijacking on First Registration
If calling /login with a phone number immediately returns a user_id without verifying ownership of that phone number (via OTP):
- Attacker calls your
/loginAPI with the victim’s phone number (+919876543210). - Your server sees it's not in DB, creates a
user_id, and hands it back to the attacker. - The attacker immediately generates an IdentityLock token on their own phone using
uid = user_id. - Attacker calls
/verifydevice. - Your server sees
NEW_REGISTRATIONand permanently binds the victim's phone number to the attacker's physical device! - When the real victim later tries to sign up, their phone will be rejected with
NEW_DEVICE.
CAUTION IdentityLock does not replace OTP / SMS verification. Device binding binds an authenticated user to a device. You must never register a device for a phone number until that phone number has been proven via SMS OTP.
2. The Decoupled API Problem (2 Roundtrips Instead of 1)
Having /login and /verifydevice as separate endpoints creates unnecessary questions:
- What gives the user their session token? If
/logingives the session, what stops an attacker from just ignoring/verifydevice? - If
/verifydevicegives the session, what stops someone from sniffing theuser_idand calling/verifydevicefrom another tool? - It doubles your network traffic and latency for every login.
3. The "Chicken-and-Egg" user_id Problem
You don't need the server to invent a user_id first just to create the token!
- The
userIdpassed toIdentityLock.getBindingToken()can simply be the phone number itself (e.g.,+919876543210). - The SDK supports any string up to 128 bytes. Using the normalized phone number as the
userIdsolves the chicken-and-egg problem completely.
The Recommended, Secure Architecture
Instead of two decoupled APIs, production apps handle this in one of two standard flows:
Flow A: Existing User Login (Single API Call)
When a user opens the app and enters their PIN/password/OTP to log in:
sequenceDiagram
actor User
participant App as Mobile App (SDK)
participant Server as Backend API
participant DB as User Database
User->>App: Enters Phone & Password / PIN
App->>App: token = getBindingToken(context, phone, nonce, 0, false)
App->>Server: POST /api/v1/auth/login<br/>Header: X-Binding-Token: <token><br/>Body: { phone: "+91...", password: "..." }
Server->>DB: Fetch user by phone
Note over Server: 1. Verify password/PIN<br/>2. verify(token, phone, user.publicKey, user.bmId)
alt Device Matches (VALID)
Server-->>App: 200 OK + Auth Token (JWT)
else New Device / Reinstall
Server-->>App: 403 Forbidden: "New device detected. OTP required."
end
Why this is secure:
- Everything happens in one atomic request.
- Both credentials (what the user knows) and device binding (what the user has) are verified together before issuing a session.
Flow B: New User Registration (OTP First, Then Bind)
When a new user signs up with their phone number:
sequenceDiagram
actor User
participant App as Mobile App (SDK)
participant Server as Backend API
participant SMS as SMS Gateway
participant DB as User Database
Note over User,App: Step 1: Prove Phone Ownership
User->>App: Enters Phone (+919876543210)
App->>Server: POST /api/v1/auth/send-otp { phone: "+91..." }
Server->>SMS: Send SMS OTP "123456"
SMS-->>User: SMS arrives
Note over User,App: Step 2: Verify OTP + Bind Device
User->>App: Enters OTP "123456"
App->>App: token = getBindingToken(context, "+91...", null, 0, false)
App->>Server: POST /api/v1/auth/verify-otp-and-register<br/>Header: X-Binding-Token: <token><br/>Body: { phone: "+91...", otp: "123456" }
Note over Server: 1. Check OTP matches<br/>2. verify(token, phone, null, null) → NEW_REGISTRATION
Server->>DB: CREATE user with phone, publicKey, and bmId
Server-->>App: 200 OK + Auth Token (JWT)
Why this is secure:
- The device is only registered after the phone number has been proven via SMS OTP.
- An attacker cannot register someone else's phone number because they won't have the SMS OTP.
- No dummy user IDs need to be passed back and forth. The phone number is the identifier.
Summary Advice for Your Backend Team
Tell your backend team:
- Don't create a separate
/verifydeviceendpoint. The binding token should simply be an HTTP header (X-Binding-Token) attached to the existing login and registration endpoints. - For Login (
POST /login): Check the password/credentials AND verify the token in that same request. - For Sign-up (
POST /register): Send an SMS OTP first. Only callverify(token, phone, null, null)after the OTP is verified, and then save the resultingpublicKeyandbmId. - Use Phone Number as
userId: The app will pass the phone number (e.g.+919876543210) as theuserIdinIdentityLock.getBindingToken().
Share this article
Help others discover this content
Don’t give fraud a way in. Secure every layer of your mobile app.
100M+
Users Protected
100+
Apps Secured
1000+
Apps Hardened