โ† Research

Client SpecKit

Client-Side SpecKit

eToro Plus (React Native) โ€” 2FA feature specification for the mobile app

๐Ÿงฉ React Native Components

src/features/2fa/
โ”œโ”€โ”€ screens/
โ”‚   โ”œโ”€โ”€ TwoFASettingsScreen.tsx      # Main 2FA settings hub
โ”‚   โ”œโ”€โ”€ TOTPSetupScreen.tsx          # TOTP setup wizard (3 steps)
โ”‚   โ”œโ”€โ”€ TOTPCodeInputScreen.tsx      # 6-digit code entry (login)
โ”‚   โ”œโ”€โ”€ WebAuthnSetupScreen.tsx      # Passkey registration
โ”‚   โ”œโ”€โ”€ WebAuthnPromptScreen.tsx     # Passkey auth during login
โ”‚   โ”œโ”€โ”€ BackupCodesScreen.tsx        # Display & copy backup codes
โ”‚   โ”œโ”€โ”€ BackupCodeInputScreen.tsx    # Enter backup code (login)
โ”‚   โ””โ”€โ”€ RecoveryScreen.tsx           # Account recovery form
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ QRCodeDisplay.tsx            # Show QR code for authenticator
โ”‚   โ”œโ”€โ”€ QRScanner.tsx                # Camera-based QR scanner
โ”‚   โ”œโ”€โ”€ CodeInput.tsx                # 6-digit OTP input with auto-focus
โ”‚   โ”œโ”€โ”€ BackupCodeCard.tsx           # Single backup code display
โ”‚   โ”œโ”€โ”€ PasskeyCard.tsx              # Registered passkey item
โ”‚   โ”œโ”€โ”€ TwoFAMethodPicker.tsx        # Choose TOTP vs Passkey
โ”‚   โ”œโ”€โ”€ SecurityBanner.tsx           # "2FA not enabled" warning
โ”‚   โ””โ”€โ”€ CountdownTimer.tsx           # Rate limit countdown
โ”œโ”€โ”€ hooks/
โ”‚   โ”œโ”€โ”€ useTwoFA.ts                  # Main 2FA state hook
โ”‚   โ”œโ”€โ”€ useTOTPSetup.ts              # TOTP setup flow state
โ”‚   โ”œโ”€โ”€ useWebAuthn.ts               # WebAuthn registration/auth
โ”‚   โ””โ”€โ”€ useBackupCodes.ts            # Backup code management
โ”œโ”€โ”€ services/
โ”‚   โ”œโ”€โ”€ twoFAApi.ts                  # API client for all 2FA endpoints
โ”‚   โ””โ”€โ”€ webauthnBridge.ts            # @simplewebauthn/browser wrapper
โ”œโ”€โ”€ context/
โ”‚   โ””โ”€โ”€ TwoFAContext.tsx             # Global 2FA status provider
โ”œโ”€โ”€ types/
โ”‚   โ””โ”€โ”€ twoFA.types.ts               # TypeScript interfaces
โ””โ”€โ”€ utils/
    โ”œโ”€โ”€ otpauthUri.ts                # Parse otpauth:// URIs
    โ””โ”€โ”€ codeFormatter.ts             # Format "123456" โ†’ "123 456"

๐Ÿ“ฑ TwoFASettingsScreen

Main hub showing current 2FA status, enabled methods, registered passkeys, and backup code count. Entry point from Account โ†’ Security.

// Key sections:
- 2FA Status badge (enabled/disabled)
- TOTP section (enable/disable toggle)
- Passkeys section (list + add new)
- Backup Codes (remaining count + regenerate)
- Recovery options

๐Ÿ”ข CodeInput

Reusable 6-digit OTP input with individual boxes, auto-advance, paste support, and haptic feedback on completion.

interface CodeInputProps {
  length?: 6 | 8;
  onComplete: (code: string) => void;
  error?: string;
  loading?: boolean;
  autoFocus?: boolean;
}

๐Ÿ“ท QRScanner

Camera view for scanning authenticator QR codes. Uses react-native-camera. Fallback: manual secret entry.

// Parses otpauth:// URI from QR
// Auto-dismisses on successful scan
// Shows "Enter manually" link below

๐Ÿ”‘ PasskeyCard

Shows registered passkey with device name, last used date, and delete option.

interface PasskeyCardProps {
  credential: WebAuthnCredential;
  onDelete: (id: string) => void;
  onRename: (id: string, name: string) => void;
}

๐Ÿ”„ User Flows

Setup TOTP
1
User navigates to Settings โ†’ Security โ†’ Two-Factor Authentication
2
Taps "Enable Authenticator App" โ€” app calls POST /2fa/totp/setup
3
QR code displayed with otpauth:// URI. "Copy secret" button below for manual entry.
4
User scans QR with Google Authenticator / Authy / 1Password
5
User enters 6-digit code from authenticator โ†’ POST /2fa/totp/verify
6
Backup codes displayed โ€” user prompted to save/screenshot. Checkbox: "I've saved these codes"
7
2FA enabled. Success animation + return to settings.
STEP 3 OF 7

Scan QR Code

QR Code

Scan with your authenticator app

Copy Secret Key
Login with TOTP
1
User enters email + password โ†’ API returns { requires2FA: true, tempToken }
2
App navigates to TOTPCodeInputScreen with 6-digit input
3
User enters code โ†’ POST /2fa/totp/validate with temp token
4
Success: receive tokens, navigate to home. Failure: shake animation + retry.
5
After 5 failures: countdown timer shown, inputs disabled for 5 min.
6
Links available: "Use Passkey" and "Use Backup Code"
Setup Passkey (WebAuthn)
1
User taps "Add Passkey" in 2FA settings
2
App calls POST /2fa/webauthn/register/options
3
@simplewebauthn/browser startRegistration() triggers OS biometric prompt
4
User authenticates with Face ID / Touch ID / PIN
5
App sends attestation โ†’ POST /2fa/webauthn/register/verify
6
User enters device name (e.g., "My iPhone"). Passkey registered.
Login with Passkey
1
After password, "Sign in with Passkey" shown as preferred (green button, above TOTP input)
2
App calls POST /2fa/passkey/authenticate/options
3
startAuthentication() triggers biometric prompt (Face ID / fingerprint)
4
User verifies โ†’ assertion sent to POST /2fa/passkey/authenticate/verify
5
Tokens received. Navigate to home. Show "Signed in with iPhone 15 Pro" toast.
Login with Passkey โ€” Conditional UI (Autofill / Passwordless)

On web: the login page uses mediation: "conditional" to show passkey suggestions in the browser's autofill dropdown โ€” no button click needed.

1
User navigates to etoro.com/login. Page calls POST /passkey/authenticate/options with empty body.
2
startAuthentication({mediation: "conditional"}) โ€” browser shows passkey in username autofill: "Sign in as yoni@etoro.com ๐Ÿ”‘"
3
User taps the autofill suggestion โ†’ biometric verification
4
Assertion (with userHandle) sent to verify endpoint โ†’ logged in instantly
5
No username. No password. No OTP. Zero friction.
PASSWORDLESS LOGIN

Welcome to eToro

Email
|๐Ÿ”‘ yoni@etoro.com

โ†‘ Browser autofill shows passkey suggestion

Login with Passkey โ€” Cross-Device (QR Code)

When the user is on a device without a registered passkey (e.g., a shared computer), they can use their phone as an authenticator via QR + Bluetooth.

1
On desktop 2FA screen, user taps "Use a passkey from another device"
2
Browser displays QR code (contains BLE advertisement + encrypted tunnel info)
3
User opens phone camera โ†’ scans QR โ†’ system detects WebAuthn request
4
Phone checks Bluetooth proximity (prevents remote relay attacks)
5
Biometric prompt on phone โ†’ Face ID / fingerprint
6
Signed assertion tunneled to desktop browser โ†’ logged in on desktop
Manage Passkeys

Settings โ†’ Security โ†’ Passkeys. Shows all registered passkeys with device info, sync status, and management options.

1
Screen loads registered passkeys via GET /2fa/passkey/credentials
2
Each passkey shown as a PasskeyCard: device name, "Synced โ˜๏ธ" or "This device only", last used date
3
Swipe left to reveal Rename and Delete actions
4
Delete requires 2FA verification (enter TOTP code or use another passkey)
5
"+ Add Passkey" button at bottom โ†’ triggers registration flow
MANAGE PASSKEYS

Your Passkeys

๐Ÿ”‘ iPhone 15 Pro
Synced โ˜๏ธ ยท Last used 2h ago
โ‹ฏ
๐Ÿ”‘ MacBook Pro
Synced โ˜๏ธ ยท Last used 1d ago
โ‹ฏ
๐Ÿ” YubiKey 5
Device only ยท Last used 5d ago
โ‹ฏ
+ Add Passkey
Passkey Nudge โ€” Upgrade Prompt

After successful SMS or TOTP login, show a bottom sheet prompting upgrade to Passkey.

1
User logs in with SMS or TOTP โ†’ success
2
After 1s delay, bottom sheet slides up with passkey promotion
3
Shows: "โšก Sign in 5x faster โ€” Use Face ID instead of codes"
4
Green button: "Set up Passkey" โ†’ navigates to passkey registration
5
Dim link: "Maybe later" โ†’ dismiss. Reappears after 7 days (max 3 times).
POST-LOGIN NUDGE
๐Ÿ”‘

Sign in faster with Passkey

Use Face ID or fingerprint instead of typing codes. It's also more secure โ€” immune to phishing attacks.

TOTP
~15s
Passkey
~3s
Set up Passkey
Maybe later
Fallback Flow โ€” Passkey Failure Recovery

When passkey authentication fails (device issue, cancelled, etc.), graceful fallback to alternative methods.

1
Passkey auth fails or user cancels biometric โ†’ "Passkey not available?" message
2
Show fallback options: โ‘  Enter TOTP code ยท โ‘ก Use backup code ยท โ‘ข Try another passkey
3
If TOTP also unavailable: "Use backup code" input shown
4
If all methods exhausted: "I can't access any method" โ†’ links to account recovery
5
Recovery flow: email verification + ID document โ†’ manual security review (24-48h)
Use Backup Code
1
On 2FA login screen, user taps "Can't access authenticator?"
2
Navigates to BackupCodeInputScreen โ€” format: XXXX-XXXX
3
User enters backup code โ†’ POST /2fa/backup-codes/verify
4
If โ‰ค3 codes remaining: warning banner prompting regeneration
Disable 2FA
1
In 2FA settings, user taps "Disable Two-Factor Authentication"
2
Confirmation dialog: "This will reduce your account security. Are you sure?"
3
User must enter current TOTP code or use passkey to confirm
4
DELETE /2fa/totp called โ†’ 2FA disabled, secrets purged

๐Ÿ—ƒ๏ธ State Management

TwoFAContext

interface TwoFAState {
  isEnabled: boolean;
  primaryMethod: 'totp' | 'webauthn' | null;
  totp: {
    enabled: boolean;
    configuredAt: string | null;
  };
  webauthn: {
    enabled: boolean;
    credentials: WebAuthnCredential[];
  };
  backupCodes: {
    remaining: number;
    generatedAt: string | null;
  };
  loading: boolean;
  error: string | null;
}

// Actions
type TwoFAAction =
  | { type: 'SET_STATUS'; payload: TwoFAStatus }
  | { type: 'TOTP_ENABLED' }
  | { type: 'TOTP_DISABLED' }
  | { type: 'PASSKEY_ADDED'; payload: WebAuthnCredential }
  | { type: 'PASSKEY_REMOVED'; payload: string }
  | { type: 'BACKUP_CODES_REGENERATED'; payload: number }
  | { type: 'SET_LOADING'; payload: boolean }
  | { type: 'SET_ERROR'; payload: string | null };

// Provider wraps entire app โ€” populated on login
// Refreshed via GET /2fa/status on each settings screen visit

Login Flow State (in AuthContext)

interface LoginState {
  step: 'credentials' | '2fa_required' | '2fa_input' | 'authenticated';
  tempToken: string | null;
  requires2FA: boolean;
  availableMethods: ('totp' | 'webauthn' | 'backup')[];
  selectedMethod: string | null;
  attemptsRemaining: number;
  lockoutUntil: number | null;  // Unix timestamp
}

๐Ÿ”— Deep Links & URI Handling

otpauth:// URI Format

otpauth://totp/eToro:user@email.com?secret=JBSWY3DPEHPK3PXP&issuer=eToro&algorithm=SHA1&digits=6&period=30

The app registers as a handler for otpauth:// URIs. When a user taps the URI (e.g., from an email), the app opens directly to the TOTP verification step.

App Deep Links

URIScreen
etoroplus://settings/2faTwoFASettingsScreen
etoroplus://settings/2fa/setup-totpTOTPSetupScreen
etoroplus://settings/2fa/add-passkeyWebAuthnSetupScreen
etoroplus://settings/2fa/backup-codesBackupCodesScreen
etoroplus://2fa/recovery?token=xxxRecoveryScreen

๐Ÿซฐ Biometric Integration

iOS (Face ID / Touch ID)

  • WebAuthn uses ASAuthorizationPlatformPublicKeyCredentialProvider
  • Passkeys synced via iCloud Keychain (cross-device)
  • Falls back to device PIN if biometric unavailable
  • Requires NSFaceIDUsageDescription in Info.plist

Android (Fingerprint / Face Unlock)

  • Uses FIDO2 API via Google Play Services
  • Passkeys synced via Google Password Manager
  • Requires android.permission.USE_BIOMETRIC
  • Minimum: Android 9+ (API 28) for FIDO2

react-native-keychain Usage

import * as Keychain from 'react-native-keychain';

// Store 2FA preference securely
await Keychain.setGenericPassword(
  'twofa_preference',
  JSON.stringify({ method: 'webauthn', skipUntil: Date.now() + 30 * 86400000 }),
  { service: 'com.etoro.plus.2fa', accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY }
);

// Check if biometric is available
const biometryType = await Keychain.getSupportedBiometryType();
// Returns: 'TouchID' | 'FaceID' | 'Fingerprint' | 'Face' | null

๐Ÿ“ฆ Client Dependencies

PackageVersionPurposePlatform
@simplewebauthn/browser^10.0WebAuthn client operationsBoth
react-native-camera^4.2QR code scanningBoth
react-native-keychain^8.2Secure storage + biometric accessBoth
react-native-qrcode-svg^6.3Render QR code for TOTP URIBoth
react-native-clipboard^1.14Copy secret / backup codesBoth
react-native-haptic-feedback^2.2Haptics on code entry / successBoth

โš ๏ธ Error States & Edge Cases

Error UI Patterns

ErrorUI Response
Invalid codeShake animation + red border + haptic
Rate limitedCountdown timer, inputs disabled
Account lockedFull-screen lockout message + support link
Network errorRetry button + offline indicator
Biometric failed"Try again" + fallback to TOTP
No backup codes leftRed warning + "Contact Support"

Edge Cases

  • App killed during TOTP setup โ†’ pending secret cleaned up after 10 min
  • Time drift on device โ†’ show warning if device clock >30s off
  • Multiple passkeys โ†’ show picker if >1 credential matches
  • Backup code with spaces/dashes โ†’ normalize before sending
  • Jailbroken/rooted device โ†’ show security warning, allow proceed
  • App backgrounded during WebAuthn โ†’ challenge valid for 120s
โ† Research ยท Architecture ยท Server SpecKit ยท Swagger โ†’