SampleBlogHtagAnalytics

SampleBlogHtagAnalytics

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):

  1. Attacker calls your /login API with the victim’s phone number (+919876543210).
  2. Your server sees it's not in DB, creates a user_id, and hands it back to the attacker.
  3. The attacker immediately generates an IdentityLock token on their own phone using uid = user_id.
  4. Attacker calls /verifydevice.
  5. Your server sees NEW_REGISTRATION and permanently binds the victim's phone number to the attacker's physical device!
  6. 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 /login gives the session, what stops an attacker from just ignoring /verifydevice?
  • If /verifydevice gives the session, what stops someone from sniffing the user_id and calling /verifydevice from 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 userId passed to IdentityLock.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 userId solves the chicken-and-egg problem completely.

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:

text
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:

text
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:

  1. The device is only registered after the phone number has been proven via SMS OTP.
  2. An attacker cannot register someone else's phone number because they won't have the SMS OTP.
  3. 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:

  1. Don't create a separate /verifydevice endpoint. The binding token should simply be an HTTP header (X-Binding-Token) attached to the existing login and registration endpoints.
  2. For Login (POST /login): Check the password/credentials AND verify the token in that same request.
  3. For Sign-up (POST /register): Send an SMS OTP first. Only call verify(token, phone, null, null) after the OTP is verified, and then save the resulting publicKey and bmId.
  4. Use Phone Number as userId: The app will pass the phone number (e.g. +919876543210) as the userId in IdentityLock.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

Emulator detected
Session blocked