How to Test Two-Factor Authentication on React Native (Complete Guide)
How to Test Two-Factor Authentication on React Native (Complete Guide)
How to Test Two-Factor Authentication on React Native (Complete Guide)
Testing two-factor authentication (2FA) in a React Native application is not just a checkbox; it is a critical security gate that protects user accounts from credential stuffing, phishing, and account takeover. When 2FA flows break in production, users are locked out, support costs rise, and trust erodes. This guide walks you through why 2FA matters, what typically fails, a detailed test matrix, manual and automated approaches, real‑world code examples, and how autonomous, persona‑driven exploration can surface bugs that scripted tests miss.
Why Two-Factor Authentication Matters in React Native Apps
React Native apps often reuse the same authentication backend as their web counterparts, but the mobile client introduces unique attack surfaces. Push notifications, biometric fallbacks, and deep‑link handling can all interfere with the delivery or verification of a second factor. If the 2FA code entry screen is poorly designed, users may mistype codes, triggering lockouts. If the fallback to SMS or authenticator apps is misconfigured, attackers can intercept or replay tokens. A robust 2FA implementation must therefore satisfy three goals:
- Correct delivery – the chosen second factor (SMS, email, authenticator app, push) reaches the user’s device within an expected time window.
- Accurate verification – the server validates the token, handles clock drift for TOTP, and enforces rate limits.
- Graceful degradation – when a factor is unavailable, the app offers a clear alternative without weakening security.
Failure in any of these areas leads to user frustration, increased support tickets, and potential compliance violations (e.g., GDPR, PSD2). Understanding these failure modes is the first step to building an effective test strategy.
Common Failure Modes of 2FA in Production
Before designing tests, it helps to catalog the ways 2FA can break in a live React Native app. The following list aggregates issues observed across multiple production releases:
| Failure Category | Typical Symptom | Root Cause |
|---|---|---|
| Delivery delay | User waits >30 s for SMS/code, then abandons | Carrier throttling, misconfigured Twilio/Nexmo provider, missing retry logic |
| Code mismatch | Verification fails despite correct entry | Server‑side TOTP window too narrow, device time skew >30 s |
| UI blocking | Modal prevents background app switch, user cannot copy code from authenticator app | Modal presented with animationType="none" and transparent={false} |
| Deep‑link hijacking | Clicking a verification link opens the wrong screen or a web view | Improper handling of Linking.addEventListener('url', …) |
| Fallback loop | After SMS failure, app repeatedly prompts for SMS without offering authenticator option | State machine not resetting after error |
| Accessibility breach | TalkBack/VoiceOver cannot focus the code input field | Missing accessibilityLabel or accessibilityTraits |
| Rate‑limit bypass | Attacker can submit unlimited wrong codes | Missing exponential back‑off on the client or server |
| Credential leakage | Code appears in logs or screenshots | Debug console.log statements left in release builds |
Each of these items maps to one or more test scenarios in the matrix that follows.
Building a Comprehensive Test Matrix for 2FA
A test matrix ensures you cover happy paths, error paths, accessibility, and security angles. Below is a detailed matrix you can adapt to your own project. Each row represents a test case; columns indicate the test type, expected outcome, and notes on automation feasibility.
| ID | Scenario | Description | Expected Result | Manual? | Automated? (Unit/Integration/E2E) | Notes |
|---|---|---|---|---|---|---|
| 1 | Happy‑path SMS | User enters phone number, receives SMS, inputs correct 6‑digit code | Login succeeds, token stored | ✅ | ✅ (E2E) | Use a test Twilio number that returns a fixed code |
| 2 | Happy‑path TOTP | User scans QR code, generates code in Google Authenticator, enters it | Login succeeds | ✅ | ✅ (E2E) | Mock time‑shift library to control TOTP window |
| 3 | SMS delivery delay >30 s | Simulate carrier latency | App shows “Resend code” after timeout, does not block UI | ✅ | ✅ (E2E with mock network) | Use react-native‑netinfo to simulate high latency |
| 4 | Incorrect TOTP (clock skew) | Device time set 2 minutes ahead | Verification fails, shows “Invalid code” | ✅ | ✅ (Unit) | Adjust jest‑fake‑timers |
| 5 | Missing fallback option | SMS fails, authenticator option not presented | User sees error and a button to try authenticator | ✅ | ✅ (Integration) | Verify state machine transitions |
| 6 | Accessibility – TalkBack focus | TalkBack enabled, navigate to code input | Input receives focus, label announced | ✅ | ❌ (Manual) | Requires device or emulator with TalkBack |
| 7 | Rate‑limit enforcement | Five consecutive wrong codes | After fifth attempt, app shows “Too many attempts, try again in 5 min” | ✅ | ✅ (E2E) | Verify back‑off timer UI |
| 8 | Deep‑link verification | User clicks verification link from email | App opens directly to verification screen with pre‑filled token | ✅ | ✅ (E2E) | Test with Linking.openURL |
| 9 | Screen capture prevention | User attempts screenshot on code screen | OS blocks screenshot or shows blank (if FLAG_SECURE used) | ✅ | ❌ (Manual) | Platform‑specific; verify via adb |
| 10 | Leak in logs | Debug build logs code to console | No code appears in adb logcat or Xcode console | ✅ | ✅ (Unit) | Scan build artifacts for console.log |
| 11 | Biometric fallback | After SMS failure, user opts for fingerprint | Biometric prompt appears, successful auth grants access | ✅ | ✅ (E2E with react-native‑touch-id) | Only on devices with fingerprint |
| 12 | Expired push notification | Push notification expires before user acts | App shows “Notification expired, request new” | ✅ | ✅ (E2E) | Use Firebase Cloud Messaging with TTL |
| 13 | Network loss mid‑flow | User loses internet after receiving code | App queues verification, retries on reconnect | ✅ | ✅ (E2E) | Simulate with npm i -g clumsy or netsh |
| 14 | International number formatting | User enters +44 7911 123456 | App normalizes to E.164, sends SMS correctly | ✅ | ✅ (Unit) | Test libphonenumber-js |
| 15 | Adverse persona – impatient user | User taps “Resend” twice quickly | App ignores second tap, shows cooldown | ✅ | ✅ (E2E) | Simulate rapid taps with Detox |
This matrix gives you a concrete backlog. The next sections show how to execute each category manually and then how to automate the repeatable parts.
Manual Testing Approach Step‑by‑Step
Even with strong automation, manual exploratory testing remains vital for uncovering UX friction, accessibility problems, and edge cases that depend on human behavior. Follow this procedure for each major release or when you suspect a regression.
- Prepare test accounts
- Create two test users in your staging environment: one with SMS enabled, one with TOTP enabled.
- Ensure each has a known password and a recovery code stored securely.
- Configure device or emulator
- Install the latest release candidate APK (Android) or IPA (iOS).
- Set system time to match the server (or deliberately offset for skew tests).
- Enable TalkBack (Android) or VoiceOver (iOS) for accessibility checks.
- For network latency tests, install a traffic shaping tool (e.g.,
clumsyon Windows,Network Link Conditioneron macOS, oradb shell tcon Android).
- Execute happy‑path scenarios
- Launch the app, navigate to the login screen, enter credentials.
- Observe the 2FA prompt: does it appear within 5 s? Is the input field clearly labeled?
- Enter the correct code received via SMS or authenticator app. Verify login succeeds and you land on the home screen.
- Repeat for both SMS and TOTP users.
- Inject failures
- Delay: Use the network tool to add 35 s latency to the SMS provider endpoint. Verify the app shows a “Resend code” button after the timeout and does not freeze the UI.
- Wrong code: Enter an intentionally incorrect TOTP code. Confirm the error message is specific (“Invalid code”) and the retry count increments.
- Rate limit: Submit five wrong codes in rapid succession. Check that the app enforces a cooldown and displays the remaining time.
- Fallback: Disable SMS (e.g., block the provider’s domain) and ensure the app offers the authenticator option without forcing the user to restart the flow.
- Accessibility checks
- With TalkBack/VoiceOver on, swipe to the code input field. Listen for the label (“Enter 6‑digit verification code”).
- Attempt to paste a code from the clipboard; ensure the paste action is announced.
- Verify that error messages are also announced when they appear.
- Security and privacy checks
- Look at
adb logcat(Android) or Xcode console (iOS) while entering a code. No code should appear in the output. - Try to take a screenshot on the verification screen. On Android, confirm the flag
SECUREprevents capture (screen appears blank in recent apps). - Verify that the QR code for TOTP is not cached in the device’s gallery (some apps inadvertently save it).
- Adverse persona simulation
- Impatient: Tap the “Resend code” button three times within one second. Ensure the app ignores extra taps and shows a cooldown notice.
- Elderly: Increase font size via system settings; confirm the input field and buttons scale correctly and remain tappable.
- Power user: Use a hardware keyboard (if supported) to navigate and submit the code via Enter key.
- Novice: Provide no instructions; observe whether the user can complete the flow without external help (e.g., tooltip) to understand what to do.
- Record observations
- Use a simple spreadsheet to log each test case ID, result (PASS/FAIL), notes, and any attached screenshots or logs.
- Flag any FAIL for immediate triage; treat any UX friction (e.g., unclear error text) as a medium‑priority bug.
Manual testing catches nuanced problems like confusing wording, missing accessibility labels, or unexpected UI animations that automated scripts often gloss over. However, repeating the same steps for every build is tedious, which leads us to automated approaches.
Automated Testing with React Native Specific Tools
Automation provides confidence that core 2FA logic remains intact across CI pipelines. React Native’s ecosystem offers several layers: unit tests for pure functions, integration tests for navigation and state, and end‑to‑end (E2E) tests that interact with the rendered UI. Below we detail each layer with concrete examples.
Unit and Integration Tests with Jest + React Native Testing Library
Start by isolating the logic that handles code verification, time‑window validation, and state transitions. Jest coupled with @testing-library/react-native lets you render components without a device.
// __tests__/TwoFactorVerification.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import TwoFactorVerification from '../src/components/TwoFactorVerification';
import * as authService from '../src/services/authService';
jest.mock('../src/services/authService');
describe('TwoFactorVerification', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('submits correct code and navigates to home', async () => {
authService.verifyCode.mockResolvedValueOnce({ success: true });
const { getByLabelText, getByRole } = render(<TwoFactorVerification />);
const codeInput = getByLabelText(/enter verification code/i);
const submitBtn = getByRole('button', { name: /verify/i });
fireEvent.changeText(codeInput, '123456');
fireEvent.press(submitBtn);
await waitFor(() => expect(authService.verifyCode).toHaveBeenCalledWith('123456'));
// Assuming the component uses navigation prop to redirect
expect(getByRole('heading', { name: /home/i })).toBeTruthy();
});
test('shows error on invalid code', async () => {
authService.verifyCode.mockResolvedValueOnce({ success: false, message: 'Invalid code' });
const { getByLabelText, getByRole } = render(<TwoFactorVerification />);
const codeInput = getByLabelText(/enter verification code/i);
const submitBtn = getByRole('button', { name: /verify/i });
fireEvent.changeText(codeInput, '000000');
fireEvent.press(submitBtn);
const errorMsg = await waitFor(() => getByRole('alert'));
expect(errorMsg).toHaveTextContent(/invalid code/i);
});
test('enforces rate limit after five failures', async () => {
authService.verifyCode.mockResolvedValueOnce({ success: false, message: 'Too many attempts' });
const { getByLabelText, getByRole } = render(<TwoFactorVerification />);
const codeInput = getByLabelText(/enter verification code/i);
const submitBtn = getByRole('button', { name: /verify/i });
for (let i = 0; i < 5; i++) {
fireEvent.changeText(codeInput, '000000');
fireEvent.press(submitBtn);
}
const errorMsg = await waitFor(() => getByRole('alert'));
expect(errorMsg).toHaveTextContent(/too many attempts/i);
// Verify that submit button is disabled
expect(submitBtn).toHaveProp('disabled', true);
});
});
What this covers
- Correct invocation of the verification service.
- Proper error handling and UI feedback.
- Rate‑limit enforcement at the UI layer (button disabled after failures).
Run these tests in CI with npm test or yarn test. They execute in milliseconds and provide fast feedback on logic changes.
End‑to‑End Tests with Detox
Detox drives the actual native UI on emulators or real devices, making it ideal for validating the full 2FA flow, including native modules like SMS retrieval or biometric prompts. The following example demonstrates a happy‑path SMS test and a failure‑injection test using a mocked network layer.
First, add Detox to your project:
npm install --save-dev detox
detox init -r jest
Configure detox.config.js for Android emulator:
module.exports = {
testRunner: 'jest',
apps: {
android.debug: {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..',
},
},
devices: {
simulator: {
type: 'android.emulator',
device: {
avdName: 'pixel_5_api_33',
},
},
},
configurations: {
'android.debug': {
device: 'simulator',
app: 'apps.android.debug',
},
},
};
Now write the test spec:
// e2e/twoFactor.test.js
describe('Two-Factor Authentication Flow', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true, permissions: { notifications: 'YES' } });
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('should login successfully with SMS code', async () => {
// Step 1: Enter credentials
await element(by.id('email-input')).typeText('user@example.com');
await element(by.id('password-input')).typeText('SecurePass!123');
await element(by.id('login-button')).tap();
// Step 2: Wait for SMS auto‑fill (using a test provider that returns a fixed code)
await waitFor(element(by.id('code-input'))).toBeVisible().withTimeout(10000);
await element(by.id('code-input')).typeText('123456'); // fixed code from mock SMS service
// Step 3: Submit verification
await element(by.id('verify-button')).tap();
// Step 4: Assert home screen appears
await waitFor(element(by.id('home-screen'))).toBeVisible().withTimeout(10000);
});
it('should show resend button after simulated SMS delay', async () => {
// Mock network delay via detox's API (requires a custom native module or jest mock)
// For demonstration, we assume a mock server endpoint that adds 40s latency
await device.launchApp({ newInstance: true, delete: true });
await device.sendToApp({ type: 'MOCK_SMS_DELAY', delayMs: 40000 });
await element(by.id('email-input')).typeText('user@example.com');
await element(by.id('password-input')).typeText('SecurePass!123');
await element(by.id('login-button')).tap();
// Initially, code input should not be visible
await expect(element(by.id('code-input'))).not.toBeVisible();
await waitFor(element(by.id('resend-button'))).toBeVisible().withTimeout(45000);
await element(by.id('resend-button')).tap();
// After resend, code appears quickly (mock server resets delay)
await waitFor(element(by.id('code-input'))).toBeVisible().withTimeout(10000);
await element(by.id('code-input')).typeText('654321');
await element(by.id('verify-button')).tap();
await waitFor(element(by.id('home-screen').toBeVisible().withTimeout(10000('home-screen'))).toBeVisible().withTimeout(10000);
});
});
Key points
- The test uses fixed mock SMS codes to avoid reliance on real carriers.
- Network delay simulation can be achieved via a custom native module that intercepts outgoing HTTP requests and adds latency, or by using a tool like
toxiproxyin a Dockerized test environment. - Detox’s
waitForandexpectAPIs make assertions about element visibility and interaction timing.
Run the suite with:
detox test -c android.debug
Detox tests typically take 2‑5 minutes per device, suitable for nightly runs or pre‑merge checks on a staging branch.
UI Automation with Appium
If you need cross‑platform coverage (iOS and Android) without writing device‑specific scripts, Appium is a solid choice. Below is a concise Appium JavaScript example that validates the TOTP flow.
First, start the Appium server:
appium
Then the test:
// wdio.conf.js (WebDriverIO + Appium)
exports.config = {
runner: 'local',
specs: ['./test/specs/**/*.js'],
capabilities: [{
platformName: 'Android',
'appium:automationName': 'UiAutomator2',
'appium:app': '/path/to/app-debug.apk',
'appium:deviceName': 'Pixel_5_API_33',
'appium:noReset': true,
}],
services: [['appium', { command: 'appium' }]],
framework: 'mocha',
mochaOpts: { ui: 'bdd', timeout: 60000 },
};
Test file:
// test/specs/totp2fa.spec.js
describe('TOTP 2FA flow', () => {
it('should accept a valid TOTP code', async () => {
// Login
await $('~email-input').setValue('totpuser@example.com');
await $('~password-input').setValue('SecurePass!123');
await $('~login-button').click();
// Wait for TOTP prompt
await $('~code-input').waitForExist({ timeout: 15000 });
// Generate a TOTP code using a known secret (for demo, we hardcode)
const totp = require('speakeasy').totp({
secret: 'JBSWY3DPEHPK3PXP',
encoding: 'base32',
});
await $('~code-input').setValue(totp);
await $('~verify-button').click();
// Expect home screen
await $('~home-screen').waitForExist({ timeout: 15000 });
});
it('should block screenshot on verification screen (Android)', async () => {
// Trigger login to reach verification screen
await $('~email-input').setValue('screenshotuser@example.com');
await $('~password-input').setValue('SecurePass!123');
await $('~login-button').click();
await $('~code-input').waitForExist({ timeout: 15000 });
// Attempt screenshot via adb (requires device connection)
const result = await driver.execute('mobile: shell', {
command: 'screencap -p /sdcard/verify.png',
});
// On a device with FLAG_SECURE, the file will be zero bytes or contain a blank image
const screenshot = await driver.execute('mobile: pull', {
path: '/sdcard/verify.png',
});
expect(screenshot.length).toBeLessThan(100); // heuristic: blank or tiny file
});
});
Appium lets you run the same script against iOS by swapping the platformName and appium:app values. This approach is valuable when you need to verify platform‑specific behaviors like the iOS UIPasteboard restrictions or Android’s FLAG_SECURE.
Using SUSATest for Autonomous Exploration (Mention SUSA)
While scripted tests validate known paths, they rarely simulate the varied behaviors of real users. SUSATest (the autonomous QA platform) can explore your React Native app without any test scripts, exercising the 2FA flow under multiple personas and surfacing issues that scripted tests miss.
To run SUSATest locally:
pip install susatest-agent
susatest explore --apk ./android/app/build/outputs/apk/debug/app-debug.apk \
--personas curious impatient novice adversarial elderly \
--flows login signup checkout \
--output ./susatest-report.json
The agent will:
- Install the APK on a connected emulator or device.
- Launch the app and begin exploring screens, tapping buttons, entering text, and handling dialogs.
- For each persona, it applies a distinct behavior profile (e.g., the impatient persona taps quickly and often uses the “Resend” button; the elderly persona enlarges font size via system settings).
- When it encounters a 2FA prompt, it attempts to retrieve a code from a mock SMS service (configured via environment variable) or generates a TOTP using a known secret.
- It records any crashes, ANRs, accessibility violations (WCAG 2.1 AA), security warnings (e.g., credentials in logs), and UX friction such as dead buttons or confusing error messages.
- After the run, you receive a JSON report with PASS/FAIL verdicts for each tracked flow and a list of discovered defects.
Why this matters for 2FA
- Curious persona may try to paste a code from the clipboard before the input field is focused, exposing focus‑management bugs.
- Impatient persona repeatedly taps “Resend” within seconds, revealing missing debounce logic.
- Adversarial persona inputs deliberately malformed strings (very long, special characters) to test for injection or buffer overflows.
- Elderly persona triggers system‑wide font scaling, highlighting layout breakpoints.
- Accessibility persona (built‑in) ensures TalkBack/VoiceOver can navigate the 2FA screen without getting stuck.
Because SUSATest does not rely on pre‑written test cases, it can discover edge cases like a race condition where the verification screen appears *before* the SMS listener is registered, causing the code to be missed. Such timing issues are notoriously hard to catch with deterministic scripts but emerge naturally when the explorer varies the pacing of interactions.
You can integrate SUSATest into your CI pipeline as a nightly job:
susatest explore --apk ./app-release.apk \
--personas all \
--flows login \
--ci \
--token $SUSA_API_TOKEN \
--output ./susatest-ci.json
The --ci flag makes the process exit with a non‑zero status if any critical defect (crash, ANR, or security leak) is found, gating merges to main.
> Note: SUSATest is mentioned here only to illustrate how autonomous, persona‑driven testing complements manual and scripted approaches. The core guidance in this guide remains applicable whether or not you use SUSATest.
Accessibility and Security Considerations for 2FA
Beyond functional correctness, 2FA implementations must satisfy accessibility guidelines and resist common attacks. Below are concise checklists you can embed in your Definition of Done.
Accessibility Checklist (WCAG 2.1 AA)
| Item | How to Verify |
|---|---|
| Labels for all inputs | accessibilityLabel on code input matches visible placeholder (“Enter 6‑digit code”). |
| Error messages announced | Trigger an invalid code; ensure TalkBack/VoiceOver reads the alert. |
| Sufficient touch target size | Buttons ≥ 48 dp; test with Android’s “Developer options → Show touch targets”. |
| Responsive to font scaling | Increase system font to 200 %; verify no clipping or horizontal scrolling. |
| No reliance on color alone | Error state uses both red text and an icon (⚠️). |
| Logical focus order | Tab/navigation moves from email → password → “Login” → code input → “Verify”. |
| Accessible fallback | If SMS fails, the alternative authenticator button is reachable without scrolling. |
Security Checklist
| Item | How to Verify |
|---|---|
| No code leakage in logs | Run adb logcat (Android) or Xcode console while entering a code; assert absence of the token. |
| Secure transmission | Confirm API calls use HTTPS with certificate pinning (use react-native-ssl-pinning or equivalent). |
| Rate limiting on client & server | After five failed attempts, client disables button; server returns HTTP 429. |
| Replay attack resistance | Server validates TOTP within a 30‑second window and increments a used‑token cache. |
| Device binding (optional) | For high‑value actions, require device‑specific key (e.g., Secure Enclave) in addition to 2FA. |
| No storage of plaintext secrets | Confirm that TOTP seeds are kept in encrypted Keychain/Keystore, not in AsyncStorage. |
| Protection against screen capture | On Android, window adds FLAG_SECURE; on iOS, prevent UIApplicationUserDidTakeScreenshotNotification from capturing the view (obscure view). |
Automated tests can verify many of these items: unit tests for pinning, integration tests for rate‑limit UI, and Detox/Appium scripts for checking FLAG_SECURE via adb shell dumpsys window windows | grep mCurrentFocus (look for secure=true). Accessibility checks remain largely manual, though tools like axe-core-react-native can flag missing labels in CI.
Edge Cases That Only Appear in Production
Even the most thorough test matrix can miss issues that manifest only under real‑world conditions. Below are several production‑only edge cases we have observed, along with mitigation strategies.
| Edge Case | Why It Happens in Production | Mitigation |
|---|---|---|
| Carrier‑specific SMS filtering | Some carriers block messages from short codes that resemble promotional spam. | Use alphanumeric sender IDs where permitted; provide a fallback to email or authenticator app. |
| Push notification silent delivery | Users may have disabled notifications or enabled battery‑optimization that delays push. | Detect missing push after a timeout and prompt the user to check notification settings. |
| Time zone changes while traveling | A user changes time zone; device clock drifts relative to server, causing TOTP failures. | Use network time (NTP) to synchronize or allow a larger verification window (±2 min) with server‑side replay cache. |
| Multiple apps sharing same keychain entry | On iOS, if two apps from the same vendor share a keychain group, the 2FA secret may be overwritten. | Scope secrets to the app‑specific access group; use KeychainAccess with service identifier unique to the app. |
| Low‑memory killer terminating background SMS receiver | Android may kill a service that listens for SMS Retriever API callbacks, causing missed codes. | Use a foreground service with a persistent notification for the SMS listener, or rely on the user‑initiated manual entry path as primary. |
| User copies code from notification shade but paste fails | Some keyboards block paste into secure fields; users then retype incorrectly. | Provide a “Use code from notification” button that programmatically fills the field via the SMS Retriever API. |
| Network switch mid‑flow (Wi‑Fi to cellular) | A captive portal redirects HTTP requests, breaking the verification call. | Listen to NetInfo changes and automatically retry the verification request after connectivity is restored. |
| Biometric lockout after too many failed attempts | Device locks fingerprint after 5 failures; user then cannot use biometric fallback. | Offer a clear “Use PIN/password” alternative after biometric error. |
| App update while 2FA screen is visible | A background install replaces the native module handling SMS, causing a crash. | Detect AppState changes and, if an update is pending, gracefully navigate away from the 2FA screen before the install proceeds. |
Mitigating these issues often requires combining client‑side resilience (retries, fallback paths) with server‑side flexibility (wider time windows, alternative delivery channels). Document any such accommodations in your run‑book so that support teams know how to assist affected users.
Checklist for 2FA Testing in React Native
Use this concise list before each release candidate. Tick each item; any unchecked box warrants investigation.
- [ ] Happy‑path SMS and TOTP flows succeed with valid credentials.
- [ ] SMS delivery delay >30 s triggers a visible “Resend code” button without UI freeze.
- [ ] Incorrect TOTP or SMS code shows a clear error and increments retry counter.
- [ ] After five consecutive failures, the app enforces a cooldown and disables the verification button.
- [ ] Accessibility tools (TalkBack/VoiceOver) can reach and interact with the code input and error messages.
- [ ] No authentication screen capture or screenshot is blocked on the verification screen (FLAG_SECURE on Android, obscured view on iOS).
- [ ] No authentication code appears in device logs (
adb logcat/ Xcode console) during entry. - [ ] Fallback to alternative 2FA method (e.g., authenticator app when SMS fails) is offered and functional.
- [ ] Deep link from email/SMS opens the app directly to the verification screen with any pre‑filled token.
- [ ] Rate limiting is enforced both on the client (UI) and the server (HTTP 429).
- [ ] Time‑skew test (±2 min) still allows verification, confirming server window adequacy.
- [ ] Battery‑optimization or notification‑disabled states prompt the user to enable notifications or switch to alternative method.
- [ ] Adverse personas (
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free