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:
- Phone (OTP)
- Email (OTP)
- Google (OAuth)
- 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
accessTokenanddeviceId -
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
accessTokenanddeviceId - The app stores
accessTokenanddeviceIdto 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:
publicKeyvoipTokenapnsTokendeviceNamesystemNamesystemVersionidentifier
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
verifiedin the email/phone login request, and uses averifyLoginendpoint 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)¶
- The user enters an email and an optional displayName.
- The app calls
AuthService.login(...)with: email = user inputphoneNumber = ""displayName = ...verified = false- plus device/publicKey fields
- The backend responds:
- semantic
verified = false - no
accessTokenand nodeviceId - The UI routes to the OTP entry screen.
A2) Verify OTP to Receive Tokens¶
- The user enters the OTP.
- The app calls
AuthService.verifyLogin(type: .email, code: ...)with: emailcode- plus device/publicKey fields
- The backend returns:
accessTokendeviceId.- The app stores
accessToken(Keychain) anddeviceId(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¶
- The user selects Sign in with Apple.
- iOS obtains
codeand an optionalappleIdToken. - The app calls
AuthService.appleLogin(code: appleIdToken: displayName:)plus device/publicKey fields. - The backend validates with Apple and returns:
accessTokendeviceId- user fields
No OTP step.
D. Continue with Google¶
- The user selects Sign in with Google.
- iOS uses
googleSignOn(from:)to obtain Google credentials (idToken/accessToken, depending on flow). - The app sends the credential to the backend (the endpoint may be in another layer; current code primarily shows client sign-in).
- The backend validates with Google and returns:
accessTokendeviceId
No OTP step.
State Machine / UI Routing (Recommended)¶
Unauthenticated- choose method
- enter email/phone or use social login
PendingOTP(email/phone only)- enter OTP
- resend OTP
Authenticated- has
accessTokenanddeviceId - 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
publicKeyor 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