How to Test Two-Factor Authentication: A Complete Guide
How to Test Two-Factor Authentication: A Complete Guide
How to Test Two-Factor Authentication: A Complete Guide
Testing two-factor authentication (2FA) is not a optional add‑on; it is a core security control that, when flawed, can expose accounts to credential stuffing, SIM‑swap attacks, and phishing. A thorough 2FA test plan uncovers gaps in the authentication flow, verifies that fallback mechanisms work, and ensures that legitimate users are not locked out by overly strict checks. This guide walks you through why 2FA testing matters, the concepts behind the various factor types, a detailed test matrix, manual and automated techniques, production‑only edge cases, accessibility considerations, and how autonomous, persona‑driven exploration can surface bugs that scripted tests miss.
Why Two-Factor Authentication Testing Matters
Security impact and compliance
When a password is compromised, a second factor is the last line of defense. If that factor can be bypassed, an attacker gains full access without needing the password. Regulations such as PSD2, NIST 800‑63B, and GDPR‑derived security requirements mandate strong customer authentication, and auditors routinely request evidence that 2FA has been validated against both functional and abuse cases.
User trust and conversion
Friction in the 2FA step leads to abandonment. Users who repeatedly fail to receive a code, encounter confusing error messages, or find the process inaccessible will either disable the feature (if allowed) or migrate to a competitor. Testing the usability of 2FA alongside its security ensures that the flow remains smooth for legitimate users while still blocking illegitimate attempts.
Common failure modes
Typical problems include:
- Delayed or missing OTP delivery due to carrier filtering.
- Time‑skew causing TOTP validation failures.
- Backup codes that are either too predictable or not invalidated after use.
- Lack of rate limiting on OTP verification endpoints.
- Inaccessible UI elements that block screen‑reader users.
Understanding these modes shapes the test matrix that follows.
Core Concepts and Threat Model
Authentication factor categories
2FA combines something you know (password) with something you have (OTP device, token, phone) or something you are (biometrics). The most common “have” factors are:
- Time‑based One‑Time Password (TOTP) apps (Google Authenticator, Authy).
- HMAC‑based One‑Time Password (HOTP) hardware tokens.
- SMS‑delivered OTP.
- Email‑delivered OTP or magic links.
- Push‑notification approval (e.g., Duo, Microsoft Authenticator).
- FIDO2/WebAuthn security keys (treated as a second factor when combined with a password).
Attack vectors relevant to 2FA
- Interception – SMS sniffing, SIM swap, email account compromise.
- Replay – Capturing a valid OTP and reusing it within its validity window.
- Brute force – Guessing OTP values when entropy is low or rate limits missing.
- Phishing – Real‑time proxy attacks that relay OTP to the legitimate server.
- Social engineering – Tricking users into approving a push notification or revealing a backup code.
- Device binding failures – Allowing authentication from a new device without proper verification.
A solid test plan addresses each vector, balancing negative testing (trying to break the factor) with positive testing (ensuring legitimate users succeed).
Test Matrix Overview
Below is a comprehensive matrix that separates test ideas by category and objective. Each row can be expanded into one or more test cases.
| Category | Sub‑category | Objective | Positive test example | Negative / edge case example |
|---|---|---|---|---|
| Happy path | Primary OTP flow | Verify successful login with correct OTP | Enter valid TOTP, receive success token | N/A |
| Backup code usage | Ensure backup codes work when primary unavailable | Use a pre‑generated backup code after losing phone | Use already‑used backup code – should be rejected | |
| Error paths | OTP mismatch | Reject incorrect OTP | Enter wrong 6‑digit code, receive error message | N/A |
| Expired OTP | Reject OTP outside validity window | Wait 35 s for a 30‑s TOTP, then submit – reject | N/A | |
| Missing OTP field | Handle absent second factor gracefully | Submit password only – receive prompt for OTP | N/A | |
| Edge cases | Clock drift | Tolerate reasonable device time skew | Shift device clock ± 2 min, TOTP still validates | Shift ± 5 min – validation fails (if tolerance too low) |
| Network latency | Ensure OTP entry works under delay | Add 2 s artificial latency, complete flow | Add 10 s latency, OTP expires before submission | |
| Concurrent sessions | Prevent session fixation across devices | Login on phone, then attempt on tablet with same session ID – should require new OTP | N/A | |
| Accessibility | Screen‑reader labels | Verify all inputs and messages are announced | Use TalkBack, confirm “Enter verification code” label is spoken | Missing aria‑label leads to silent field |
| Color contrast | Meet WCAG AA for text vs background | Measure contrast ratio ≥ 4.5:1 | Low‑contrast error text fails | |
| Security | Rate limiting | Throttle OTP verification attempts | After 5 failed attempts, endpoint returns 429 | No limit allows unlimited brute force |
| Replay protection | Reject OTP used more than once | Submit same TOTP twice within window – second rejected | Accepting replay indicates flaw | |
| Phishing resistance | Detect proxy‑based real‑time phishing | Use a mock MITM that forwards OTP – server detects anomaly (e.g., IP mismatch) | No detection – successful phishing | |
| Performance | Latency under load | Ensure OTP verification stays < 200 ms under peak | Load test with 500 req/s, measure response time | Spikes > 500 ms indicate bottleneck |
| Localization | Language‑specific messages | Confirm translated error strings appear | Switch UI to French, trigger OTP mismatch – see French message | Missing translation key shows raw code |
| Recovery | Account recovery flow | Validate fallback when both factors lost | Initiate recovery via email, set new password & OTP | Recovery allows resetting OTP without email verification |
The matrix above can be used as a starting point; teams should add product‑specific rows (e.g., QR‑code scanning for authenticator enrollment, hardware token NFC tap).
Manual Testing Approaches
Exploratory testing checklist
A tester should begin with a scripted baseline (happy path) and then deviate to discover hidden issues. A lightweight checklist helps maintain coverage:
- Credential entry – Verify password field behaves normally; ensure autocomplete does not leak the OTP.
- OTP delivery – Request OTP via each channel (SMS, email, push) and confirm receipt within expected time.
- OTP input – Test paste, manual typing, auto‑fill from password manager, and voice input.
- Error handling – Trigger each error state (wrong code, expired, network failure) and validate messaging.
- Fallback paths – Use backup codes, recovery email, and alternative device enrollment.
- Accessibility – Run screen‑reader, high‑contrast mode, and keyboard‑only navigation.
- Session behavior – Log in on two devices simultaneously; observe whether sessions are independent or shared.
- Rate limit probing – Perform rapid failed OTP submissions and note any throttling or CAPTCHA appearance.
- Device binding – After OTP verification, remove the trusted device from account settings and try to log in again; expect a new OTP request.
- Log inspection – Confirm that authentication success/failure events are logged with sufficient detail for forensic analysis.
Real‑device and carrier testing
- Use physical SIM cards from multiple carriers to capture differences in SMS delivery timing and filtering.
- Test with airplane mode toggled on/off to simulate intermittent connectivity.
- Employ a SIM‑swap‑simulation tool (e.g., a programmable GSM modem) to verify that the service detects a change in ICCID and triggers additional verification.
Network condition emulation
- Apply tools like
tc(Linux traffic control) or Charles Proxy to introduce latency, jitter, and packet loss. - Verify that OTP entry windows are respected; if the server allows a 30‑second TOTP window, the client should still be able to submit after a 2‑second delay but not after a 35‑second delay.
Accessibility tooling
- Run axe‑core or Lighthouse to catch missing labels, insufficient contrast, and inaccessible custom widgets.
- Test with VoiceOver (iOS/macOS) and TalkBack (Android) to ensure that error messages are announced after an OTP failure.
- Confirm that the OTP field accepts input from assistive technologies such as switch control.
Capturing and inspecting OTPs
- For SMS testing, use a dedicated test number with a service like Twilio that forwards messages to a webhook; log the exact OTP and timestamp.
- For email, use a disposable inbox (Mailinator, MailSlurp) and retrieve the OTP via API to validate timing.
- For push notifications, set up a mock push service that logs the payload sent to the device; ensure the payload contains a nonce that the server can verify.
Automated Testing Strategies
Unit and contract tests for backend logic
- TOTP validation – Unit test the verification function with known secrets, timestamps, and drift tolerances.
- Hotp counter handling – Ensure that the server increments the counter only on successful validation and rejects reuse.
- Backup code hashing – Test that backup codes are stored as salted hashes and that each code can be used once.
- API contract – Use OpenAPI/Swagger validation to confirm that the
/verify-otpendpoint returns the correct HTTP status codes and error bodies for each scenario.
UI automation with Appium (Android) and Playwright (Web)
- Mock OTP provider – Instead of relying on real SMS or email, expose a test endpoint that returns a pre‑determined OTP when the application requests delivery. This makes tests deterministic.
- Time‑control for TOTP – Many libraries allow injecting a custom clock. In test builds, replace
System.currentTimeMillis()with a controllable mock so you can fast‑forward or rewind time without waiting. - Push‑notification mock – Implement a lightweight WebSocket server that the app connects to for push challenges; the test can send an “accept” or “reject” payload on demand.
- Sample Appium script (Java)
@Test
public void totpHappyPath() {
// 1. Login with username/password
driver.findElement(By.id("username")).sendKeys("testuser");
driver.findElement(By.id("password")).sendKeys("Secure!23");
driver.findElement(By.id("loginBtn")).click();
// 2. Retrieve OTP from mock service
String otp = restTemplate.getForObject("http://mock-otp/service?user=testuser", String.class);
// 3. Enter OTP and submit
driver.findElement(By.id("otpCode")).sendKeys(otp);
driver.findElement(By.id("verifyBtn")).click();
// 4. Assert landing page
Assert.assertTrue(driver.findElement(By.id("welcomeMsg")).isDisplayed());
}
- Sample Playwright test (TypeScript)
test('email OTP flow', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'P@ssw0rd!');
await page.click('#login');
// Fetch OTP from a test mailbox
const otp = await mailbox.getLatestOtp('user@example.com');
await page.fill('#otp', otp);
await page.click('#verify');
await expect(page.locator('#dashboard')).toBeVisible();
});
Handling OTP generation in tests
- For TOTP, compute the expected code in the test using the same secret and time step as the server (libraries like
otpliborpyotpmake this trivial). - For HOTP, maintain a counter variable that increments after each successful validation and assert that the server’s counter matches.
Dealing with rate limits and CAPTCHA
- In test environments, disable or lower rate‑limit thresholds, or provide a bypass header (e.g.,
X-Test-Mode: true) that the application respects only in non‑production builds. - If a CAPTCHA appears, use a test‑only mode that returns a predetermined token, or employ a third‑party solving service exclusively for test runs (never in production).
Simulating delivery failures
- Configure the mock SMS/email endpoint to return HTTP 500 or delay the response by a configurable interval. Verify that the UI shows an appropriate “Didn’t receive the code?” link and that retry logic works.
- Test the fallback to voice call or alternative email address when the primary channel fails.
Validating backup code behavior
- Pre‑populate a set of backup code hashes in the test database.
- Attempt to use each code once; ensure subsequent attempts are rejected.
- Verify that after all codes are consumed, the system prompts for recovery instead of accepting any further backup code.
Cross‑browser and cross‑device matrix
- Run the same Playwright script against Chromium, Firefox, and WebKit to catch browser‑specific quirks (e.g., autofill behavior).
- Execute Appium tests on a range of Android API levels and screen sizes to ensure the OTP field adapts correctly.
Production‑Only Edge Cases and Observability
Carrier‑level filtering and SMS fraud prevention
In production, carriers may block messages that look like OTPs (e.g., repeated digits, known spam patterns). A test that uses a generic “123456” OTP may pass in a sandbox but fail when sent via a real carrier. To catch this, monitor delivery success rates per carrier and alert when a sudden drop occurs.
Phone number recycling and number reassignment
When a user abandons a number, the carrier may reassign it to a new subscriber. If the service still trusts the old number for OTP delivery, the new owner could gain access. Production tests should include a “number change” flow: verify that removing a number from the account invalidates any pending OTPs tied to it.
Authenticator app clock drift
Users’ devices may drift several minutes due to poor synchronization. While many servers allow a ± 1‑window tolerance, some enforce stricter checks. In production, log the time difference between the server’s UNIX timestamp and the timestamp embedded in the TOTP (recoverable from the OTP and secret) to detect excessive drift.
Backup code exhaustion
Power users may exhaust their backup codes after multiple device losses. Observe whether the service automatically generates a new set after a threshold or forces the user through recovery. Lack of regeneration can lead to lock‑out scenarios.
Push notification fatigue and mistaken approval
Frequent push prompts can cause users to approve without reading. In production, measure the rate of “approve” versus “deny” actions per user; a high approve rate with low login attempts may signal fatigue. Consider implementing number‑matching (show a digit on the login screen that the user must enter in the push prompt) to mitigate this.
Concurrent session hijacking
If the application does not bind the OTP validation to the specific session that initiated the login, an attacker who intercepts an OTP could use it in a different browser or device. Production telemetry should correlation‑auth events: session ID, IP address, and user agent must match across the password and OTP steps.
Internationalization quirks
OTP messages sometimes contain hard‑coded English strings (“Your code is: 123456”). When the UI language switches, the message may remain English, causing confusion for non‑English speakers. Production monitoring should scan OTP templates for language placeholders and flag any missing localization.
Fallback mechanism abuse
Some services allow a user to bypass 2FA by answering a security question after a failed OTP attempt. In production, track the frequency of fallback usage; a spike may indicate credential stuffing attempts that are probing the weaker recovery path.
Observability practices
- Emit structured logs for each 2FA event:
attempt_id,user_id,factor_type,result,timestamp,ip,device_id. - Export metrics to a Prometheus‑compatible endpoint:
otp_requests_total,otp_success_total,otp_failure_by_reason. - Set alerts on:
- OTP failure rate > 5 % over 5 min.
- Sudden increase in backup code usage.
- Repeated OTP requests from the same IP within a short window (possible brute force).
Accessibility and Inclusive Testing
WCAG considerations for 2FA UI
- Labeling – Every OTP input must have an associated
oraria-labelthat clearly states its purpose (e.g., “Enter the 6‑digit code sent to your phone”). - Error messages – Must be perceivable; use
aria-live="assertive"so screen readers announce them immediately. - Contrast – Text and icons inside the OTP field must meet at least AA contrast (4.5:1 for normal text).
- Touch target size – Buttons for “Resend code” and “Verify” should be ≥ 44 × 44 dp.
- Keyboard navigation – Users must be able to move focus to the OTP field via
Tab, submit viaEnter, and navigate away without getting trapped.
Testing with assistive technology
- Screen reader – Enable VoiceOver/iOS or TalkBack/Android, navigate to the login flow, and verify that the OTP field is announced as an editable text box with a helpful hint.
- High contrast mode – Switch the OS to high contrast; ensure that the OTP field border and placeholder text remain visible.
- Switch control – Use a switch device to scan through inputs; confirm that the OTP field is selectable and that the “Resend” button can be activated.
- Voice input – Test dictation tools (e.g., Windows Speech Recognition, macOS Voice Control) to ensure they can insert numbers into the OTP field without triggering unwanted commands.
Alternative authentication methods
If the service offers a biometric factor (fingerprint, Face ID) as the second factor, verify that the biometric prompt is accessible:
- The system dialog must announce its purpose.
- Users who cannot provide biometrics must have a fallback to OTP or backup code.
Inclusive test data
- Include test users with varied abilities: low vision, motor impairment, cognitive load.
- Use personas that represent elderly users (slower interaction speed, possible tremor) and power users (rapid repeated attempts).
- Document any friction observed and feed it back to the design team for iterative improvement.
Using Autonomous, Persona‑Driven Exploration (SUSA) to Find 2FA Bugs
SUSA explores an application without pre‑written scripts, generating real user interactions based on configurable personas. When pointed at a login flow that includes 2FA, it can discover issues that static test suites often miss because it varies timing, input methods, device states, and intention.
How SUSA approaches 2FA
- Curious persona – Taps every visible element, tries to long‑press the OTP field, attempts to paste from clipboard, and rotates the device while the OTP timer is running. This often reveals issues with clipboard handling or orientation‑locked layouts.
- Impatient persona – Rapidly taps the “Resend code” button multiple times in quick succession, then tries to submit an OTP before the previous request has finished. This can expose race conditions where the server accepts an OTP from an outdated request.
- Novice persona – Enters the OTP slowly, makes a typo, then uses the backspace key extensively. It also tries to submit a non‑numeric string, checking whether the application sanitizes input correctly.
- Adversarial persona – Attempts to submit OTPs generated from known weak seeds (e.g., all zeros, repeating patterns) and tries to reuse an OTP after the validity window by manipulating device clock.
- Accessibility persona – Enables TalkBack, increases font size, and switches to high contrast, then attempts to complete the flow. This surfaces missing labels or contrast problems that manual testers might overlook if they rely solely on visual inspection.
Example findings from SUSA runs
- In a banking app, the curious persona discovered that holding down the OTP field triggered a context‑menu offering “Paste” and “Select All”. Selecting “Paste” inserted the previous OTP from the clipboard, allowing replay if the user had previously copied a valid code. The fix was to disable paste for the OTP field.
- The impatient persona found that rapidly pressing “Resend code” five times caused the backend to issue five separate OTPs, but the UI only displayed the most recent one. If a user then entered an older code, the server rejected it, leading to confusion and support calls. Rate‑limiting the resend endpoint resolved the issue.
- The accessibility persona, using TalkBack at 200 % font size, reported that the error message “Invalid code” was truncated and not announced fully because the container had
overflow: hidden. Adjusting the container’smax-heightfixed the announcement.
Integrating SUSA into CI
- Upload the APK or provide the web URL to the SUSA CLI (
susatest-agent run --app my-app.apk --personas curious,impatient,novice,adversarial,accessibility). - Define a baseline – the first run creates a map of screens and transitions; subsequent runs compare against this map to detect new dead ends or newly reachable states.
- Fail the build if SUSA reports a crash, ANR, or a WCAG‑AA violation discovered during the 2FA flow.
- Leverage cross‑session learning – after each run, SUSA remembers which OTP entry attempts led to dead ends (e.g., wrong code submissions that triggered a lockout). Future runs prioritize exploring alternative paths (backup code, recovery) increasing coverage over time.
By combining SUSA’s exploratory power with deterministic automated checks for cryptographic correctness, teams achieve both breadth (real‑world user variability) and depth (formal validation of the OTP algorithm).
Checklist for 2FA Testing
| Area | Item | ✅ Done? |
|---|---|---|
| Basic flow | Password + correct OTP leads to successful session | |
| Incorrect OTP shows clear error and does not authenticate | ||
| Expired OTP is rejected with appropriate message | ||
| Delivery | OTP arrives via SMS, email, and push within expected window | |
| “Resend code” works and does not spam the user | ||
| Fallback to alternative delivery channel functions | ||
| Backup codes | Each backup code works exactly once | |
| Used backup codes are rejected on reuse | ||
| Exhausted backup codes trigger recovery flow | ||
| Rate limiting | After N failed OTP attempts, further attempts are blocked or delayed | |
| Rate limit resets after cool‑down period | ||
| Security | OTP cannot be replayed within its validity window | |
| Server detects OTP reuse across different sessions/IPs | ||
| Push‑notification approval includes session binding (nonce) | ||
| Accessibility | All fields have associated labels or aria‑labels | |
| Error messages are announced by screen readers | ||
| Contrast ratios meet WCAG AA | ||
| Touch targets ≥ 44 dp | ||
| Device binding | New device requires fresh OTP after trusted device removal | |
| Existing trusted sessions remain valid after password change (if policy allows) | ||
| しばらく | Clock drift tolerance is documented and tested (± 2 min typical) | |
| Recovery | Account recovery flow works when both password and 2FA factors are lost | |
| Recovery does not weaken the second factor (e.g., does not bypass OTP) | ||
| Observability | Success and failure events are logged with sufficient detail | |
| Metrics on OTP latency, failure reasons, and backup code usage are exported | ||
| Alerts fire on anomalous patterns (high failure rate, bursts of resend) |
Mark each item as completed during a test cycle; any unchecked box indicates a gap that requires further investigation.
Closing Takeaways
Testing two‑factor authentication is a blend of security validation, usability verification, and resilience engineering. A solid program starts with a clear threat model, enumerates the various factor types, and builds a test matrix that covers happy paths, error conditions, edge cases, accessibility, and production‑only realities. Manual exploratory testing remains essential for catching subtle UX flaws and device‑specific quirks, while automated unit, contract, and UI tests guarantee that the cryptographic core and API contracts stay correct under change.
Production environments introduce variables that are impossible to reproduce in a lab: carrier filtering, number reassignment, clock drift, and user behavior under fatigue. Observability—structured logs, metrics, and alerts—closes the loop by detecting regressions that slip through pre‑release checks.
Accessibility is not an afterthought; the 2FA step must be perceivable, operable, and understandable for users with diverse abilities. Integrating persona‑driven, autonomous exploration tools like SUSA adds a powerful layer of discovery, surfacing issues that arise from real‑world variability in timing, input methods, and assistive technology use.
When teams treat 2FA as a first‑class feature—testing it with the same rigor as core application logic—they reduce the risk of account takeover, improve user confidence, and maintain compliance with evolving security standards. Use the checklist, tables, and techniques presented here as a living reference, and revisit them whenever the authentication flow evolves, new factor types are added, or regulatory guidance shifts.
---
*This guide is intentionally platform‑agnostic. Replace tool‑specific snippets (Appium, Playwright, Twilio, etc.) with equivalents that match your stack, but keep the underlying principles: verify correctness, validate delivery, harden against abuse, and ensure every user can succeed.*
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