Skip to content
Bugwise
Menu
Features & Flows

Two-Factor Authentication (OTP)

How 2FA and one-time codes work (SMS, email, authenticator apps, push approval, backup codes), what usually breaks in OTP screens, and how to test codes, resends, lockouts and recovery on mobile and web.

35 min intermediate Mobile & web

You log in to your banking app, and before you see your balance it asks: “Enter the 6-digit code we sent to •••• 0187”. You switch to Messages, read the code, switch back, type it, and you’re in. That second step is two-factor authenticationTwo-factor authenticationLogging in with two proofs, usually a password plus a one-time code.Open in glossary → (2FA), and the code is a one-time password (OTP).

2FA exists because passwords leak. People reuse them, phishing sites collect them, old databases get stolen. With 2FA, a stolen password alone isn’t enough: the attacker also needs your phone. For testers that makes the OTP screen a small screen with a big job. It must stop attackers, and at the same time let a real person in, fast, while they juggle apps, slow SMS and a code that expires.

How it works

The three kinds of factors

A “factor” is a type of proof. Real 2FA combines two different types. A password plus a security question is still one type (two things you know).

FactorMeaningExamples
KnowledgeSomething you knowPassword, PIN
PossessionSomething you havePhone (SMS, authenticator app, push), security key
InherenceSomething you areFace ID, fingerprint (see biometric authenticationBiometric authenticationUnlocking a saved login with Face ID, Touch ID or a fingerprint.Open in glossary →)

The flow, step by step

  1. First factor. The user enters email and password. The server checks them but does not start a full session yet. It remembers “password OK, waiting for step 2”.
  2. The server issues a challenge. For SMS or email it generates a random code (usually 6 digits), stores it with an expiry (often 5 to 10 minutes) and sends it. For an authenticator app nothing is sent: the app already knows how to compute the code.
  3. The user enters the code. They type it, paste it, or accept the phone’s autofill suggestion. Many apps submit automatically once all digits are in.
  4. The server checks it. Right code, not expired, not used before, and not too many wrong attempts? Then the code is marked as used and a sessionSessionThe period during which a user stays logged in without entering their credentials again.Open in glossary → starts.
  5. Optional: remember this device. The server gives the device a long-lived token, so this phone or browser skips step 2 for, say, 30 days.

Where the code comes from

MethodHow it worksWatch out for
SMSServer texts a random code to the phone numberSlow or missing SMS, roaming, SIM swap attacks, cost per SMS
EmailServer emails a code or a linkSpam folder, delays, weaker if the email account uses the same password
Authenticator app (TOTP)App and server share a secret at setup; both compute a new code every 30 seconds from the secret and the current timePhone clock out of sync, lost phone, setup via QR code
Push approvalThe app on a trusted phone shows “Is this you logging in?” with Approve/DenyUsers approving pushes they didn’t start (“push fatigue”), no internet on the phone
Backup / recovery codesA list of single-use codes shown once at setup, to use when the phone is lostEach code works once, the list can be regenerated (old list must stop working)

SMS is the most familiar method, but not the strongest. The US NIST digital identity guidelines (SP 800-63B) classify codes sent by SMS or phone call as a restricted method, because of risks like SIM swaps and interception, and expect services to offer users an alternative. That’s why many products also support authenticator apps or security keys.

Authenticator apps and clock skew

TOTPTOTPTime-based one-time password: an authenticator app and the server compute the same code every 30 seconds.Open in glossary → (time-based one-time password, defined in RFC 6238) is what Google Authenticator, Microsoft Authenticator and similar apps use. Nothing is sent over the network: the server and the app both compute the code from the shared secret and the current time, in steps of (by default) 30 seconds. That means the phone’s clock matters. Try it:

Authenticator app

Northbank · leo@test.com

––– –––

2 min slowcorrect2 min fast

Server accepts

Same secret + server time. One step either side is a common tolerance.

  • previous

    ––––––

  • current

    ––––––

  • next

    ––––––

Accepted: phone and server are in the same 30-second step.
Move the slider: a phone clock that is set manually and drifts a minute or two is a real cause of "my codes never work" tickets. (Codes here are simplified, not real TOTP.)

Many servers also accept the code from one step before and after the current one, to cover small clock differences and the seconds it takes to type. If the phone clock is set manually and drifts further, every code is “wrong”. That’s clock skew, and it’s worth a test: set the device time manually a minute or two off and see what the app tells the user.

OTP input: the small details that matter

The code field looks simple, but it’s where most user frustration happens. Everything from Text Field applies, plus:

  • One-time code autofill. iOS suggests a code from Messages (or Mail) above the keyboard when the field is marked as a one-time-code field. Android can do the same with the SMS Retriever / User Consent APIs, and Chrome on Android supports the WebOTP API for websites. Test that the suggestion appears and fills the field correctly.
  • Paste. People copy codes from SMS, email or a password manager. Pasting 318452, 318 452 or 318452 (with spaces) should all work.
  • Numeric keyboard. The field should open the number pad, but keep the value as text: codes like 042917 start with a zero, and a number would lose it.
  • Auto-submit. Submitting when the last digit is entered is convenient, but it must fire exactly once, and the user must still be able to fix a typo.
  • One field or six boxes? Both are common. A single field is simpler and usually works better with autofill, paste and screen readers. Separate boxes look nice but often break paste, backspace, and focus movement. If you test boxes, try paste, backspace across boxes, and tapping a box in the middle.

Example requirements

What usually goes wrong

#BugWhat you seeTypical severity
1Code never expiresA code from this morning still worksHigh (security)
2Code replayThe same code logs in twice (two devices, or after logout)High (security)
3Old code works after resendSpec says resending cancels older codes, but they still workMedium–High
4No lockoutUnlimited guesses: 6 digits are only 1,000,000 combinationsCritical (security)
5No resend cooldownTapping Resend sends an SMS every time (spam, cost, rate limit by the SMS provider)Medium–High
6Leading zero lostCodes like 042917 always fail because the app treats the code as a numberHigh
7Paste brokenPasting 318 452 is cut off or rejectedMedium
8Auto-submit twiceError “code already used” right after a successful loginMedium
9Autofill doesn’t workiOS/Android never suggest the code from the SMSMedium
102FA can be bypassedGoing straight to a deep link or a protected URL after step 1 skips the codeCritical (security)
112FA off without re-authAnyone holding an unlocked phone can disable 2FA in SettingsHigh (security)
12No way back inLost phone and no backup codes or recovery path, so support is the only optionHigh

Find the bug

Northbank’s SMS code screen below has six bugs. Under the phone there’s a Messages panel (the SMS that arrive on the phone), a clock you can move forward, the spec, and a server log. Explore like a tester:

  • Log in, read the SMS and enter the code. Use the From Messages suggestion too.
  • Tap Resend code a few times quickly. Then try the older codes.
  • Copy a code from Messages (it contains a space) and paste it.
  • Enter wrong codes many times.
  • Watch the server log for every request, and look closely at what’s sent.

🐞 Bug hunt: Northbank SMS code

This demo has 6 hidden bugs. Explore it like a tester, then check your findings.

Found0/6
9:41

Northbank

leo@test.com
••••••••••

Password is already filled in. Step 2 is the SMS code.

Clock

09:41:00

Messages · Northbank

No messages yet. Tap Log in on the phone.

From the spec

  • A code expires after 5 minutes and works once.
  • Only the newest code is valid; resending cancels older codes.
  • "Resend code" is available 30 seconds after the last SMS.
  • After 5 wrong codes, the user must log in again.
  • Pasting a code with spaces works; codes can start with 0.

Server log

  • Requests appear here…
  1. 1

    Hint: Tap Resend twice, then enter the first code you received.

  2. 2

    Hint: Resend until you get a code that starts with 0. Read the verify request in the server log.

  3. 3

    Hint: Copy a code from Messages (it is shown as "318 452") and paste it.

  4. 4

    Hint: Enter wrong codes six or more times and watch the "failed" count in the log.

  5. 5

    Hint: Tap Resend code five times in a row.

  6. 6

    Hint: Enter a correct code (or tap the From Messages suggestion) and watch what happens after you are logged in.

Test scenarios

Functional

  • Correct code logs in; the session starts only after the code (not after the password).
  • Each method the product supports: SMS, email, authenticator app, push approval, backup codes.
  • Resend sends a new code; the newest code works.
  • Autofill: the iOS “From Messages” suggestion and the Android equivalent fill the code; auto-submit logs in once.
  • Paste of 318452, 318 452 and the code with leading or trailing spaces.
  • Trust this device: the next login on this device skips the code; another device still asks; revoking in Settings makes it ask again.
  • Enable 2FA: scan the QR code in an authenticator app, confirm with a code, see and save backup codes.
  • Disable 2FA, change phone number, regenerate backup codes: each asks for the password again (or another strong check).

Negative

  • Wrong code, empty field, 5 digits, letters.
  • Expired code (wait past the limit, or ask for a test environment with a short expiry).
  • Used code: log in, log out, log in again with the same code. It must fail (no replay).
  • Old code after resend: compare with the spec.
  • Too many wrong codes: the lockout kicks in at exactly the agreed number, and resending or restarting the app doesn’t reset the counter.
  • Resend too fast: the button is disabled during the cooldown, and the server rejects requests that bypass it.
  • Old backup codes after generating a new list.
  • Skipping step 2: after a correct password, try opening a protected screen directly (a deep linkDeep linkA link that opens a specific screen inside an app instead of just its home screen.Open in glossary → on mobile, a URL like /accounts on the web). It must ask for the code first.

Edge cases

  • Leading zeros: make sure you see at least one code starting with 0 (resend a few times, or ask a developer for a fixed test code).
  • Expiry boundaries: just before and just after the limit (for example 4:50 and 5:10 for a 5-minute code).
  • Lockout boundaries: 4 wrong then 1 right (works and resets the counter); exactly 5 wrong (locked).
  • Clock skew for authenticator apps: set the phone time manually 30, 60 and 120 seconds off.
  • Code arrives late: the SMS for code A arrives after you’ve requested code B.
  • Switching apps: leave the app to read the SMS, return, and check the screen, the typed digits and the timer are still there. See Interruptions.
  • Phone number formats: international numbers, numbers changed recently, roaming.
  • Two devices: start login on the web, receive a push on the phone, and Deny it; the web login must fail.
  • Lost phone: log in with a backup code, then use the same backup code again (must fail).

UI & usability

  • The screen says where the code was sent (masked number or email) and how long it’s valid.
  • Numeric keyboard opens; the field accepts digits only and doesn’t lose leading zeros.
  • Resend shows a visible countdown; after resending, the user sees that a new code was sent.
  • Errors are specific and helpful: “That code isn’t right” vs “This code has expired, request a new one”.
  • There’s a clear link for “I can’t access my phone” (backup code or recovery).
  • With separate boxes: paste fills all boxes, backspace moves to the previous box, and focus never gets stuck.

Security basics

Defensive checks you can do without special tools:

Accessibility

Example test cases

TC-2FA-001Log in with a valid SMS code

Preconditions

  • 2FA by SMS is enabled for leo@test.com
  • User is logged out

Steps

  1. Log in with the correct email and password
  2. Open the SMS and enter the 6-digit code

Expected result

The code is submitted once and the Home screen opens. One verify request in the log. No error appears.

TC-2FA-002Code with a leading zero is accepted

Preconditions

  • Test environment returns the fixed code 042917, or resend until a code starts with 0

Steps

  1. Log in with the correct password
  2. Enter 042917
  3. Inspect the verify request

Expected result

User is logged in. The request sends "042917" as a string.

TC-2FA-003Pasted code with a space is accepted

Steps

  1. Copy "318 452" from the SMS
  2. Paste it into the code field

Expected result

The field shows 318452 and the code is submitted. No error.

TC-2FA-004Resend cancels the previous code

Steps

  1. Receive code A
  2. Wait 30 seconds and tap "Resend code"
  3. Receive code B
  4. Enter code A

Expected result

Code A is rejected. Code B logs in.

TC-2FA-005Lockout after 5 wrong codes

Steps

  1. Enter 5 different wrong codes
  2. Enter the correct code
  3. Tap Resend and try a new code

Expected result

After the 5th wrong code the user is sent back to the password step. The correct code no longer works, and resending does not reset the counter.

TC-2FA-006Resend cooldown

Steps

  1. Request a code
  2. Try to tap "Resend code" immediately
  3. Wait 30 seconds and tap again

Expected result

Resend is disabled with a countdown for 30 seconds, then sends exactly one new SMS.

TC-2FA-007Code cannot be reused

Steps

  1. Log in with a valid code
  2. Log out
  3. Log in with the password and enter the same code within 5 minutes

Expected result

The code is rejected ("This code has already been used").

TC-2FA-008Disabling 2FA requires the password

Preconditions

  • User is logged in with 2FA enabled

Steps

  1. Open Settings > Security
  2. Turn off two-factor authentication

Expected result

The app asks for the password (and/or a code) before turning 2FA off, then sends a notification email.

Tip: click a test case to expand it.

Mobile vs web

  • SMS autofill: on iOS, the code from Messages appears above the keyboard; on Android, test the autofill or consent prompt your app uses. Check it with the app in the foreground and when the SMS arrives while the keyboard is closed.
  • Switching apps: read the code in Messages or an authenticator app and come back. The app must not restart the flow, clear the field, or send a new code. Also test a phone call arriving while you’re on the code screen (Interruptions).
  • Push approval: test Approve, Deny, ignoring it, and approving after it expired. Test with notifications turned off for the app: there must be a fallback.
  • Authenticator on the same phone: copy the code from the authenticator app and paste it. Set the phone time manually to test clock skew.
  • New phone or reinstall: is “trust this device” reset? Are users stuck if the authenticator app wasn’t moved to the new phone? That’s when backup codes and recovery matter.
  • Biometrics: some apps let Face ID or fingerprint replace the code on a trusted device. Test the fallback when biometrics fail.

Checklist

2FA / OTP checklist

0 of 15 checked

Check your understanding

Quick quiz

0/6 answered · 0 correct

  1. 1.Which pair is real two-factor authentication?

  2. 2.Customers say "sometimes my SMS code doesn't work". You notice every failing code starts with 0. What is the likely cause?

  3. 3.An authenticator code is always rejected on one phone, but works on others. What should you check first?

  4. 4.After a successful login, an alert says "This code has already been used". The log shows two verify requests. What is the bug?

  5. 5.Which test best checks protection against guessing codes?

  6. 6.In Settings, a logged-in user can turn off 2FA with a single tap. Why is that a problem?

Key takeaways

  • 2FA combines two different types of proof (know, have, are). The OTP is the “have” part, so it must be short-lived, single-use and limited in attempts.
  • Test the code lifecycle: expiry, reuse, old codes after resend, lockout, and resend cooldown, always against the spec and at the boundaries.
  • Treat codes as text: leading zeros, spaces in pasted codes, autofill and paste must all work.
  • Test the real-life journey: switching apps to read the SMS, late SMS, wrong phone clock, lost phone and backup codes.
  • Enabling, disabling and changing 2FA settings need re-authentication, and nobody should reach the account before the code is verified.

Finished this page?Nice work, this page is complete!

Mark it as done to track your progress. It's saved in this browser, no account needed.

Its review cards are now in Bites ⚡
Esc

Type to search the whole site.