Common Two-Factor Authentication Bugs and How to Catch Them
Two‑factor authentication (2FA) adds a critical barrier against credential theft, yet implementation flaws turn that barrier into a weak point that attackers can bypass or abuse. This guide walks thro
Common Two-Factor Authentication Bugs and How to Catch Them
Two‑factor authentication (2FA) adds a critical barrier against credential theft, yet implementation flaws turn that barrier into a weak point that attackers can bypass or abuse. This guide walks through the most common 2FA bugs, shows how they appear to users, explains why they happen, and gives concrete steps to reproduce, detect, fix, and prevent each issue. The material is organized for engineers who need a reference they can bookmark, with a test matrix, manual and automated approaches, real‑world examples, and a short checklist you can paste into your CI pipeline.
---
Why 2FA Bugs Matter
When a login flow relies on a password plus a second factor, the overall security is only as strong as the weakest link in that chain. A flaw in the OTP generation, delivery, validation, or session handling can let an attacker:
- Replay a valid code after it has already been used.
- Guess or brute‑force a six‑digit code because rate limits are missing or too lax.
- Intercept SMS through SIM‑swap or SS7 attacks when the delivery channel is not protected.
- Bypass 2FA entirely by triggering a fallback to an less‑secure method (e.g., email link) that the attacker controls.
- Leave a session alive after a successful 2FA challenge, enabling session fixation or credential stuffing.
These issues are not theoretical; they have appeared in production releases of banking apps, enterprise SSO portals, and consumer services. Detecting them early requires a combination of unit‑level checks, integration tests, and exploratory testing that mimics real user behavior—including the varied personas that autonomous QA platforms like SUSA can simulate.
---
Overview of a Typical 2FA Flow
Understanding the baseline helps you spot where things can go wrong. A typical TOTP‑based 2FA flow looks like this:
- Username/password submission → server validates credentials and creates a temporary session token (often marked
pre‑2fa). - Challenge generation → server creates a nonce, stores it tied to the temporary session, and sends the user a prompt to enter a six‑digit code.
- User enters OTP → client sends the code plus the temporary session token.
- Server verification → server recomputes the expected OTP using the shared secret and the current time step, checks the nonce for reuse, validates the code within the allowed window, and if successful upgrades the session to
authenticated. - Post‑login actions → the authenticated session is used for subsequent API calls; any attempt to reuse the pre‑2fa token is rejected.
Variations include push‑notification approvals, email‑based magic links, or backup codes, but the core security properties remain: *the OTP must be used once, must be time‑bound, must be tied to the specific login attempt, and must not be reusable after a successful login.*
---
Bug Pattern 1: Time‑Based OTP Drift and Clock Skew
Cause
TOTP algorithms (RFC 6238) derive the code from a shared secret and the current Unix time divided by a timestep (usually 30 seconds). If the server’s clock is significantly ahead or behind the user’s device, the computed OTP will not match, causing legitimate users to be locked out. Conversely, an overly generous validation window (e.g., accepting codes from ± 2 steps) can widen the attack surface for replay.
User Impact
- Legitimate users receive “invalid code” errors despite entering the correct value from their authenticator app.
- Support tickets spike after daylight‑saving changes or when users travel across time zones.
- Attackers may exploit a wide window to reuse a captured code within the extended validity period.
Reproduction Steps
- Set up a test environment where you can manipulate the system clock (e.g., using
faketimeon Linux or adjusting the VM’s time). - Register a TOTP secret for a test account.
- Shift the server clock forward by 45 seconds (1.5 timesteps).
- Attempt login with a freshly generated OTP from the authenticator app.
- Observe failure despite the code being valid for the user’s device.
Detection
- Unit test – mock
time.time()and assert that validation fails outside a ± 1 step window unless explicitly configured. - Integration test – drive the login flow with a real authenticator library (e.g.,
pyotp) while the test harness adjusts the server clock via an API or environment variable. - Automated exploratory – a persona that repeatedly changes device time (simulating travel) can surface drift‑related lockouts; SUSA’s “curious” persona often tries edge‑case inputs like unusual timestamps.
Fix
- Keep server time synchronized via NTP with a maximum offset of < 100 ms.
- Log and alert on clock drift exceeding a threshold (e.g., 500 ms).
- Restrict the acceptance window to a single timestep (± 0) or at most ± 1 step, and document the rationale.
Prevention
- Include a health‑check endpoint that returns server time; CI can verify it against a trusted NTP source.
- In containerized deployments, bake NTP synchronization into the image entrypoint.
- Add a feature flag that, when enabled, forces a strict one‑step window for new rollouts, allowing gradual migration.
---
Bug Pattern 2: OTP Replay / Missing Nonce Enforcement
Cause
After a successful OTP validation, the server must ensure that the same code cannot be used again. Some implementations only check the OTP value against the expected value and neglect to store a nonce or a used‑code flag tied to the login attempt.
User Impact
- An attacker who intercepts a valid OTP (e.g., via shoulder surfing, malware, or a compromised SMS gateway) can reuse it to gain access even after the legitimate user has logged in.
- The legitimate user may notice an unexpected session or receive a notification of a login from an unfamiliar location.
Reproduction Steps
- Capture a valid OTP during a legitimate login (use a proxy like Burp Suite to log the request).
- Complete the login flow normally.
- Immediately replay the captured OTP request (same payload, same temporary session token) to the verification endpoint.
- Observe whether the server accepts the replay and grants a second authenticated session.
Detection
- Unit test – after a successful verification, call the verification function again with the same OTP and assert that it returns an error.
- Integration test – use a test client to perform login, store the OTP, then send a second verification request; check for a 400/401 response.
- Exploratory – a persona modeled after an “adversarial” user will try to resend the same OTP multiple times; SUSA’s adversarial profile includes rapid‑fire request loops that can surface missing nonce checks.
Fix
- Generate a cryptographically random nonce (or use the temporary session ID) when presenting the OTP challenge.
- Store the nonce in server‑side storage (e.g., Redis with a short TTL) linked to the temporary session.
- On verification, confirm that the nonce has not been used before; mark it as used after a successful check.
- Invalidate the nonce after a short timeout (e.g., 2 minutes) to prevent storage bloat.
Prevention
- Enforce the nonce check in the authentication middleware; make it impossible to bypass via configuration.
- Write a contract test that asserts the verification endpoint returns an error for duplicate nonces.
- Review all OTP‑related endpoints in security‑focused code‑review checklists.
---
Bug Pattern 3: Insecure Delivery Channel (SMS Interception, SIM Swap)
Cause
When the second factor is delivered via SMS, the security depends on the integrity of the mobile telecom layer. Vulnerabilities such as SIM‑swap social engineering, SS7 exploits, or malware that reads incoming SMS can give an attacker the OTP without needing the victim’s device.
User Impact
- Users receive an OTP they did not request, indicating a possible interception attempt.
- Successful compromise leads to unauthorized account access, often discovered only after fraudulent transactions.
- Trust in the service erodes, especially for high‑value accounts (banking, crypto).
Reproduction Steps
*This pattern is harder to reproduce in a pure unit‑test setting because it relies on external telecom behavior. However, you can simulate the risk:*
- Configure the account to use SMS OTP delivery.
- Use a tool like SMS‑Getter (open‑source Android app) to forward incoming SMS to a controlled number.
- Trigger a login that sends an OTP via SMS.
- Verify that the forwarded message arrives at the attacker’s number and can be used to complete login.
Detection
- Dependency check – ensure your service does not fall back to SMS as the sole 2FA method for high‑risk actions.
- Monitoring – alert on abnormal spikes in SMS OTP requests per account or per IP range.
- Exploratory testing – a persona that mimics an “impatient” user may request multiple OTPs in quick succession; SUSA can be configured to send repeated SMS requests and watch for lack of rate limiting.
Fix
- Deprecate SMS as a primary 2FA factor for sensitive operations; favor TOTP, push notifications, or hardware tokens.
- If SMS must be retained, implement additional safeguards:
- Require user confirmation of the last‑four digits of the phone number before sending an OTP.
- Integrate with carrier‑level SIM‑swap detection APIs (where available).
- Encourage users to enable account‑level PINs with their carrier.
- Add a short‑lived, cryptographically signed token to the SMS body that the server can verify, reducing the value of a plain‑text interception.
Prevention
- Maintain a feature flag that can disable SMS OTP globally; run regular drills to verify the flag works.
- Include SMS‑specific abuse scenarios in threat‑modeling sessions.
- Track delivery success rates; a sudden drop may indicate carrier‑side filtering or blocking, prompting investigation.
---
Bug Pattern 4: Missing Rate Limiting / Brute Force on OTP Entry
Cause
The OTP verification endpoint may lack limits on the number of attempts per session, per IP, or per account. An attacker can therefore try all 1,000,000 possible six‑digit codes (or a reduced set if they know the time window) until they hit the correct value.
User Impact
- Legitimate users may be locked out after too many failed attempts if the service mistakenly treats brute force as a user error.
- Successful brute force leads to account takeover without needing the victim’s device.
- Excessive traffic can cause denial‑of‑service on the verification service.
Reproduction Steps
- Obtain a valid temporary session token (by completing the password step).
- Write a script that sends OTP verification requests with sequential codes from
000000to999999. - Monitor the response codes; a
200 OKor session upgrade indicates success. - Note the number of requests required and any throttling responses (e.g.,
429 Too Many Requests).
Detection
- Unit test – call the verification function ten times with incorrect OTPs and assert that the tenth call returns a
429or similar error if rate limiting is enabled. - Integration test – deploy the service behind a mock load‑generator (e.g.,
locust) and verify that after a threshold (say 5 attempts per minute) the response status changes. - Exploratory – SUSA’s “power user” persona can be tuned to issue rapid OTP attempts; the platform will record whether the server responds with increasing delays or error codes.
Fix
- Implement per‑account and per‑IP rate limits (e.g., max 5 attempts per 5 minutes).
- Use an exponential back‑off or CAPTCHA after a certain number of failures.
- Log each failed attempt with sufficient context (IP, user‑agent, timestamp) for abuse detection.
- Return a generic error message (“Invalid code”) to avoid leaking whether the OTP was close to correct.
Prevention
- Enforce rate‑limit checks in a shared authentication middleware so that all OTP‑related endpoints inherit the policy.
- Write a contract test that asserts the middleware returns
429after exceeding the limit. - Include rate‑limit verification in your security‑testing checklist and run it on every pull request.
---
Bug Pattern 5: Fallback to Less Secure Methods (Email Link, Backup Code) Bypass
Cause
Some services allow users to fall back to an alternative verification method if the primary 2FA fails (e.g., “Didn’t receive the code? Try email instead”). If the fallback method is not protected by the same rigor (no rate limiting, weak token, or predictable URL), an attacker can trigger the fallback and bypass the stronger factor.
User Impact
- Users may inadvertently weaken their security by relying on a fallback that an attacker can guess or intercept.
- Attackers who have compromised the user’s email (or can predict backup codes) can gain full access without needing the OTP device.
- The existence of a fallback can create confusion; users may not realize they have switched to a weaker method.
Reproduction Steps
- Begin a login flow and intentionally fail the OTP challenge (e.g., enter a wrong code).
- Observe whether the UI offers a fallback link or button (e.g., “Send code to email”).
- Trigger the fallback and complete the alternative verification (e.g., click a magic‑link sent to email).
- Verify that the resulting session grants the same privileges as a successful OTP login.
- Assess whether the fallback endpoint lacks rate limiting, uses predictable tokens, or does not invalidate after use.
Detection
- Unit test – simulate a failed OTP attempt and assert that the fallback endpoint requires the same authentication factors (e.g., a CSRF token, rate limit, or one‑time token).
- Integration test – chain a failed OTP attempt with a fallback request and verify that the fallback still validates the original password session and does not allow a fresh anonymous request.
- Exploratory – SUSA’s “novice” persona often clicks help links after errors; the platform can be configured to follow those links and test the resulting endpoint for security gaps.
Fix
- Remove fallback to weaker methods for high‑risk actions; if a fallback is necessary, protect it with the same strength as the primary factor (e.g., require re‑entry of password, enforce rate limiting, use cryptographically bounded tokens).
- Ensure that any fallback token is single‑use, short‑lived, and bound to the original login attempt (e.g., include the temporary session ID in the token).
- Provide clear UI messaging that indicates which factor is being used, so users are aware of the security level.
Prevention
- Maintain a matrix of allowed verification methods per action; enforce it in the gateway layer.
- Write a security unit test that attempts to bypass each 2FA method using only the fallback path and expects failure.
- Document fallback logic in the threat model and review it whenever a new authentication method is added.
---
Bug Pattern 6: Improper Handling of Backup Codes
Cause
Backup codes are static strings meant for emergency access. If they are stored in plaintext, logged, or transmitted insecurely, they become a high‑value target. Additionally, if the service does not enforce one‑time use or does not invalidate the set after a certain number of uses, an attacker who obtains a single code can reuse it indefinitely.
User Impact
- Loss of backup codes equates to loss of the account recovery mechanism.
- Reuse of a backup code enables persistent attacker access without triggering OTP challenges.
- Inadequate protection may lead to credential stuffing attacks using harvested backup‑code lists from data leaks.
Reproduction Steps
- Generate a set of backup codes for a test account (usually via the account security page).
- Capture the HTTP response that returns the codes (intercept with a proxy).
- Verify whether the codes appear in plaintext in logs, response bodies, or emails.
- Use one code to log in successfully.
- Attempt to log in again with the *same* code; observe whether the server rejects it as used.
- Exhaust all codes and try a previously used code again to see if the server mistakenly re‑accepts it.
Detection
- Static analysis – search the codebase for
logger.info,System.out.println, or similar calls that might output backup codes. - Dependency check – ensure backup codes are stored as salted hashes (e.g., bcrypt) rather than plaintext.
- Unit test – after verifying a backup code, call the verification function again with the same code and assert a failure.
- Exploratory – SUSA’s “elderly” persona may request backup codes repeatedly; the platform can monitor whether the service enforces a cooldown or rate limit on code generation.
Fix
- Generate backup codes using a CSPRNG, store only a salted hash, and show the plaintext code to the user exactly once (e.g., in a modal that advises copying).
- Mark a code as used immediately after successful verification and remove it from the user’s available set.
- Implement a regeneration policy: after a certain number of codes have been used, force the user to create a new set.
- Never log or email backup codes; if transmission is required (e.g., via secure envelope), encrypt with a key known only to the user.
Prevention
- Add a lint rule that flags any logging of variables named
backup_codeor similar. - Include backup‑code handling in your authentication security test suite.
- Review the backup‑code flow during each security sprint, ensuring that the threat model covers theft, replay, and leakage.
---
Bug Pattern 7: Session Fixation After 2FA Success
Cause
After a user successfully completes the 2FA challenge, the application may retain the pre‑2fa session identifier and simply mark it as authenticated. An attacker who can force a victim to start a login session (e.g., via a link that sets a known session cookie) can then wait for the victim to complete 2FA and inherit the authenticated session.
User Impact
- The victim believes they have logged in securely, but the attacker now has an active session with the same privileges.
- Detection is hard because the session appears legitimate; anomalous activity may be the only clue.
Reproduction Steps
- Obtain a valid session cookie (e.g.,
sessionid=attacker123) from the login page before any credentials are entered. - Send the victim a link that includes this cookie (via URL parameter, same‑site cookie manipulation, or a cross‑site request that sets the cookie).
- Victim visits the link, enters username/password, and completes the 2FA challenge.
- Attacker uses the known session cookie to access protected resources.
- Verify that the attacker’s requests succeed without re‑prompting for credentials.
Detection
- Unit test – after a successful 2FA verification, assert that the session ID has changed (i.e., a new session identifier is issued).
- Integration test – simulate the fixation scenario with two test clients: one that sets a known session, another that completes login; verify that the second client does not inherit the first’s session ID.
- Exploratory – SUSA’s “curious” persona often manipulates cookies and URL parameters; the platform can be configured to attempt session fixation and report whether the server rotates the session identifier post‑2FA.
Fix
- Upon successful 2FA validation, invalidate the pre‑2fa session and create a brand‑new session identifier (with fresh entropy, secure flags, and HttpOnly).
- Ensure that the old session cannot be upgraded; any attempt to use it after 2FA should result in a redirect to the login page.
- Rotate session IDs also after any privilege change (e.g., password change, sensitive transaction).
Prevention
- Enforce session‑rotation logic in a centralized authentication filter so that all login paths inherit it.
- Write a contract test that attempts to reuse an old session ID after 2FA and expects a
401or redirect to login. - Include session fixation in your threat model and verify it during each penetration‑test cycle.
---
Bug Pattern 8: Inconsistent UI State Leading to Phishing
Cause
The login page may display mixed messaging—for example, showing a “Enter your OTP” field while simultaneously presenting a “Forgot password?” link that triggers a password‑reset flow. If the UI does not clearly indicate which factor is being requested, an attacker can craft a look‑alike page that tricks the user into entering their OTP on a attacker‑controlled site, believing they are still on the legitimate service.
User Impact
- Users may unknowingly give away their OTP to a phishing site, leading to immediate account takeover.
- Trust in the service erodes as users notice inconsistent branding or unexpected redirects.
Reproduction Steps
- Navigate to the login flow and capture the HTML of the OTP entry screen.
- Identify any elements that deviate from the expected branding (different logo, off‑domain action URLs, missing HTTPS indicators).
- Create a mirror page hosted on a controlled domain that replicates the OTP field but posts to an attacker’s endpoint.
- Send a phishing email or message with a link to the mirror page.
- Observe whether users (in a controlled test group) enter their OTP without noticing the discrepancy.
Detection
- Automated DOM inspection – run a test that checks the OTP form’s
actionattribute resolves to the same origin as the login page. - Visual regression – use a tool like
StorybookorChromaticto capture screenshots of the OTP screen and compare against a baseline; flag deviations in logo, colors, or layout. - Exploratory – SUSA’s “impatient” persona may quickly click through fields without reading surrounding text; the platform can log whether the user proceeds despite missing security cues (e.g., missing lock icon).
Fix
- Ensure the OTP entry form’s action URL is absolute and uses the same scheme, host, and port as the login page.
- Display a clear, persistent indicator (e.g., a lock icon and the service’s domain) near the OTP field.
- Avoid mixing unrelated links (password reset, account recovery) on the same screen; if necessary, separate them into a distinct “Help” section that is visually distinct.
- Implement Content Security Policy (CSP) headers that restrict form submissions to trusted origins.
Prevention
- Add a UI‑lint step that verifies all form actions on authentication pages are same‑origin.
- Include a security checklist item for “no mixed‑context links on 2FA screens.”
- Run regular phishing simulations using internal test accounts to measure user susceptibility.
---
Bug Pattern 9: Code Expiration Not Enforced Server‑Side
Cause
Some implementations rely on the client to enforce OTP expiration (e.g., showing a countdown timer) but fail to reject expired codes on the server. An attacker who records a valid OTP can then use it after the client‑side timer has expired, as long as the server still accepts it.
User Impact
- Increases the window for replay attacks beyond the intended 30‑second period.
- Users may feel safe because their authenticator app shows the code as expired, yet the server still accepts it.
Reproduction Steps
- Record a valid OTP from the authenticator app at time T.
- Wait until the client‑side countdown shows the code as expired (e.g., 45 seconds later).
- Submit the OTP to the verification endpoint.
- Verify whether the server accepts the code and grants access.
Detection
- Unit test – mock the current time to be two timesteps ahead of the OTP’s generation time and assert that verification fails.
- Integration test – drive the login flow with a real TOTP library, then manually adjust the system clock forward by one timestep before sending the verification request; check for rejection.
- Exploratory – SUSA’s “curious” persona can be configured to deliberately delay OTP submission; the platform will record whether the server still accepts late codes.
Fix
- Perform expiration validation strictly on the server side: compute the expected OTP for the current timestep and for the previous timestep (if allowing a one‑step window) and reject anything else.
- Do not rely on client‑sent timestamps; derive the timestep from the server’s own clock.
- Log attempts with expired codes for abuse detection.
Prevention
- Add a unit test that explicitly tries to use an OTP from a past timestep and expects failure.
- Enforce a rule in your authentication library that the verification function receives only the user‑submitted code and derives the time internally.
- Review any client‑side expiration UI to ensure it is purely cosmetic and does not affect security logic.
---
Bug Pattern 10: Missing or Incorrect Cryptographic Binding (e.g., Not Verifying Signature)
Cause
When using push‑notification or QR‑code based 2FA (e.g., FIDO2, WebAuthn), the authenticator signs a challenge that the server must verify. If the server omits the signature check or verifies it against the wrong public key, an attacker can replay a previously captured signature or forge a response.
User Impact
- Counterfeit authentication attempts succeed, leading to account takeover.
- Users may notice nothing unusual because the flow appears to complete normally.
Reproduction Steps
- Initiate a push‑notification based login and capture the signed challenge sent by the authenticator (via a proxy or debugging tool on the device).
- Replay the exact same signed payload to the verification endpoint after the original completed.
- Check whether the server accepts the replay and grants a session.
- Alternatively, tamper with the payload (e.g., change the username) and see if the server still accepts it due to missing signature verification.
Detection
- Unit test – after a successful verification, feed the same signature bytes again and assert the function returns an error.
- Integration test – use a WebAuthn test harness (e.g.,
@webauthn/test-helpers) to generate a credential, complete registration, then attempt to reuse the authenticator’s signature for a new login; expect failure. - Exploratory – SUSA’s “adversarial” persona can be scripted to capture and resend authentication responses; the platform will log whether the server detects replay.
Fix
- Verify the cryptographic signature using the public key associated with the registered authenticator device.
- Include the challenge, origin, and bindings (such as
rpId) in the verification computation. - Maintain a replay cache (e.g., a short‑lived set of used challenge IDs) to ensure each signature is used only once.
- Return distinct error codes for signature failure vs. other validation issues to aid debugging without leaking specifics.
Prevention
- Adopt a well‑audited library (e.g.,
webauthn4j,python-webauthn) rather than rolling your own verification. - Write a contract test that attempts to reuse a signature and expects a
401. - Include cryptographic binding checks in your authentication security test suite and run them on every CI build.
---
Bug Pattern 11: Inadequate Logging and Alerting for 2FA Failures
Cause
If failed OTP attempts are not logged with sufficient context (IP, user‑agent, timestamp, reason), abuse campaigns can go unnoticed. Likewise, missing alerts on anomalies (e.g., a sudden surge of failed attempts from a new geographic region) delay response.
User Impact
- Attackers can brute‑force or replay OTPs repeatedly without triggering alarms, increasing the chance of eventual success.
- Operators lack forensic data to investigate incidents after they occur.
Reproduction Steps
- Generate a burst of incorrect OTP submissions (e.g., 20 attempts in 10 seconds) from a single IP.
- Check the service’s logs for entries corresponding to these attempts.
- Verify whether the log contains the IP address, the reason for failure (invalid code, expired, used nonce), and a timestamp.
- Confirm whether an alert (email, PagerDuty, etc.) is fired based on a threshold (e.g., > 5 failures/minute).
Detection
- Log inspection – automate a grep for
2FA_failin your log aggregation system and validate the presence of required fields. - Metric monitoring – expose a Prometheus counter for
2fa_attempts_total{result="failure"}and ensure your alerting rule triggers on a spike. - Exploratory – SUSA’s “power user” persona can be configured to emit a high volume of failed OTP requests; the platform can verify that logs and metrics increase accordingly.
Fix
- Log each OTP verification attempt with: user ID (or pseudonym), IP address, user‑agent, timestamp, failure reason, and whether a nonce was used.
- Ensure logs are written to a secure, append‑only store (e.g., CloudWatch, ELK) with retention sufficient for forensic analysis.
- Implement rate‑based alerts: if failures > N per minute per account or per IP, trigger an incident.
- Avoid logging the actual OTP value; instead log a hash or simply “invalid”.
Prevention
- Add a logging template to your authentication middleware; enforce its use via code review.
- Write a unit test that verifies the logger is called with the expected fields on both success and failure.
- Include logging and alerting checks in your security‑runbook and test them during game‑day exercises.
---
Bug Pattern 12: Improper Handling of Device Registration / Trust
Cause
Some services allow users to “trust” a device after a successful 2FA login, skipping the second factor on subsequent logins from that device. If the trust token is predictable, poorly protected, or not tied to a strong device identifier, an attacker can clone or steal the trust token and bypass 2FA entirely.
User Impact
- Users who rely on trusted‑device convenience may unknowingly weaken their security posture.
- Attackers who gain access to a user’s browser storage (via XSS, malware, or a compromised extension) can extract the trust cookie and use it from another machine.
- The illusion of security persists because the login still asks for a password, but the second
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