Skip to content

Authentication Flow (Continue / 2-in-1)

Goal

Authentication in the Continue (2-in-1) app follows a combined Sign Up + Sign In flow. Users choose a login method and provide an identifier (email/phone) or use a social provider (Google/Apple). The backend decides whether the account is new or already exists.

The app supports four authentication methods:

  1. Phone (OTP)
  2. Email (OTP)
  3. Google (OAuth)
  4. Apple (OAuth)

Key Principles

1) OTP Gate for Email and Phone

For Email and Phone, a session is issued only after OTP verification succeeds.

  • When starting login with email/phone:
  • verified = false
  • The backend does not return accessToken and deviceId
  • The backend triggers OTP delivery (or requires the app to call resend OTP, depending on backend implementation)

  • Only after OTP verification succeeds:

  • The backend returns accessToken and deviceId
  • The app stores accessToken and deviceId to access protected APIs

2) Social Login Does Not Require OTP

For Google and Apple, there is no OTP verification step. The backend issues tokens based on the result of OAuth validation.

3) Complete User and Device Info Is Always Included

The iOS client always attaches fields for device registration and E2EE public key during login and verification:

  • publicKey
  • voipToken
  • apnsToken
  • deviceName
  • systemName
  • systemVersion
  • identifier

These fields are built in AuthService.swift.


Data Contract (iOS Models Reference)

AuthResponse

File: ecall/Modules/Authentication/Models/AuthResponse.swift

struct AuthResponse: Decodable {
    let userId: UInt64?
    let deviceId: UInt64?
    let email: String?
    let phoneNumber: String?
    let displayName: String?
    let accessToken: String?
    let publicKey: String?
}

Expected behavior by flow: - Email/Phone pre-verify step: accessToken == nil and deviceId == nil - After successful OTP verify: accessToken != nil and deviceId != nil

Note: The iOS code currently sends verified in the email/phone login request, and uses a verifyLogin endpoint to finish token issuance.


Endpoints (Based on iOS AuthService)

Concrete endpoint URLs are mapped in APIEndpoint (not listed here). The iOS layer calls:

  • login(email:phoneNumber:displayName:verified:)
  • verifyLogin(email:phoneNumber:code:type:)
  • verifyUser(email:phoneNumber:code:type:) (account/identity verification)
  • resendOTP(email:phoneNumber:)
  • appleLogin(code:appleIdToken:displayName:)
  • googleSignOn(...) (client-side Google credential retrieval)

Detailed Flow

A. Continue with Email (Sign In / Sign Up)

A1) Start Login (Not Verified Yet)

  1. The user enters an email and an optional displayName.
  2. The app calls AuthService.login(...) with:
  3. email = user input
  4. phoneNumber = ""
  5. displayName = ...
  6. verified = false
  7. plus device/publicKey fields
  8. The backend responds:
  9. semantic verified = false
  10. no accessToken and no deviceId
  11. The UI routes to the OTP entry screen.

A2) Verify OTP to Receive Tokens

  1. The user enters the OTP.
  2. The app calls AuthService.verifyLogin(type: .email, code: ...) with:
  3. email
  4. code
  5. plus device/publicKey fields
  6. The backend returns:
  7. accessToken
  8. deviceId.
  9. The app stores accessToken (Keychain) and deviceId (as per storage strategy).

A3) Resend OTP (If Needed)

  • The app calls AuthService.resendOTP(email:phoneNumber:).

B. Continue with Phone (Sign In / Sign Up)

Same as email, but use type: .phoneNumber.

B1) Start Login (Not Verified Yet)

  • verified = false
  • The backend does not return accessToken/deviceId

B2) Verify OTP to Receive Tokens

  • The app calls verifyLogin(type: .phoneNumber)
  • The backend returns accessToken/deviceId

C. Continue with Apple

  1. The user selects Sign in with Apple.
  2. iOS obtains code and an optional appleIdToken.
  3. The app calls AuthService.appleLogin(code: appleIdToken: displayName:) plus device/publicKey fields.
  4. The backend validates with Apple and returns:
  5. accessToken
  6. deviceId
  7. user fields

No OTP step.


D. Continue with Google

  1. The user selects Sign in with Google.
  2. iOS uses googleSignOn(from:) to obtain Google credentials (idToken / accessToken, depending on flow).
  3. The app sends the credential to the backend (the endpoint may be in another layer; current code primarily shows client sign-in).
  4. The backend validates with Google and returns:
  5. accessToken
  6. deviceId

No OTP step.


  • Unauthenticated
  • choose method
  • enter email/phone or use social login
  • PendingOTP (email/phone only)
  • enter OTP
  • resend OTP
  • Authenticated
  • has accessToken and deviceId
  • routes to the main app

Transition conditions: - Email/Phone: - login(verified=false) -> PendingOTP - verifyLogin(success) -> Authenticated - Social: - appleLogin/googleLogin(success) -> Authenticated


Error Handling (Suggested Mapping)

  • Invalid/expired OTP: show error, allow re-entry, support resend.
  • Network/server error: show toast/banner and provide retry.
  • Missing publicKey or device fields: block early (if required by backend) or retry after key generation/import.

iOS Implementation Notes

  • AuthService.login(...) currently sends both email and phoneNumber; for clarity, set the unused field to "" to avoid ambiguity.
  • verifyLogin(...) is the critical step that issues tokens for email/phone.
  • Fields publicKey/voipToken/apnsToken/... are attached on both login and verifyLogin so the backend can:
  • create/update the device session
  • store the public key for E2EE