2FA Flow Diagram Guide
How to draw a 2FA flow diagram that covers what real ones need — the half-authenticated state, lockout branches, and three ready-to-edit examples: TOTP, SMS, and enrollment.
What is a 2FA flow diagram?
A 2FA flow diagram shows what happens between "the password was correct" and "the user is signed in" — the second factor. It is a sequence diagram, because the step involves several parties that are easy to conflate: your app, whatever verifies the code, and the thing that produces the code, which is usually not under your control at all.
The single idea that makes this diagram worth drawing is that 2FA splits login into two transactions, and creates a state between them. After the password check the user is neither logged out nor logged in. They are something else — half authenticated — and that state needs its own short-lived credential, its own expiry, and its own attempt counter. Diagrams that skip it draw an arrow straight from "password ok" to "enter your code" and quietly imply that the server is holding the user's identity in a variable somewhere. That implication is where most real 2FA vulnerabilities live.
The second reason is that this flow is mostly failure branches. The happy path is four arrows and nobody argues about it. What teams argue about — and what support tickets are made of — is: how many wrong codes before we lock the account, what happens when the SMS never arrives, what a user does when their phone is in a river, whether an old code can be replayed. Every one of those is a branch, and a branch you have not drawn is a branch someone will improvise in a hotfix.
This page draws only the second factor. The password step appears in the skeleton just to show where the handoff is.
What a 2FA flow diagram has to show
Six things. The first is the one that separates a real diagram from a marketing illustration:
- The half-authenticated state, as a named thing. Draw the token the server hands back after the password check —
pre_auth_token,mfa_challenge, whatever you call it — with its expiry on the arrow. If your diagram has no such token, your diagram is describing a session that already exists before the second factor. - Who generates the code and who verifies it. For TOTP these are two different parties that never talk to each other — the authenticator app and your server independently compute the same number from a shared secret. Drawing the app as a lifeline makes that obvious; describing it in prose never does.
- The attempt counter and what happens when it runs out. "Wrong code" is not one branch, it is two: wrong-but-you-may-retry, and wrong-and-you-are-done. Put the numbers on the diagram.
- The time window. A TOTP code is valid for a 30-second step, usually with one step of tolerance either side; an SMS code has a TTL you chose. Both belong on the diagram, because "why did a code that looked right get rejected" is a support question with a clock-skew answer.
- Single use. A verified code must be marked consumed, or a code observed over someone's shoulder stays valid for the rest of its window. This is one self-arrow and it is the cheapest thing on this list to draw.
- A way back in. A 2FA diagram with no recovery path is a diagram of a system that will lock out its own users. Recovery codes, a backup factor, or a support process — draw whichever one you actually have.
The skeleton, with no failure branches at all. Notice how short it is — that is the entire happy path, and it is why a diagram that stops here tells you almost nothing.
View the Mermaid source
sequenceDiagram
participant User
participant App as Web App
participant Auth as Auth Service
participant DB as User Store
User->>App: username and password
App->>Auth: verify credentials
Auth->>DB: look up the password hash
DB-->>Auth: hash and two_factor_enabled
Auth-->>App: password ok, second factor required
App->>User: ask for the 6 digit code
User->>App: 123456
App->>Auth: verify the code
Auth-->>App: code ok, session issued
App->>User: signed inHow to draw a 2FA flow diagram
Three steps. Step 2 is where the diagram stops being a picture and starts being a review.
Step 1 · Decide which factor, and who owns it
The three common second factors produce three different diagrams, and the difference is entirely about which participants you do not control:
TOTP (authenticator app) — the code generator is an app on the user's phone with no network connection to you. It never appears in an arrow to your server. Your diagram has a lifeline that only ever talks to the user.
SMS — there is a third-party gateway in the middle, which means an extra participant, an extra failure mode (delivery), and an extra cost per attempt that makes resend rate limits a business decision, not just a security one.
Passkeys / hardware keys — the browser mediates, and the interesting arrows are challenge and signature rather than a code a human retypes. Different enough that it deserves its own diagram, not a branch in this one.
Pick one per diagram. If you support two, draw two — the moment you fold SMS fallback into a TOTP diagram, both paths get harder to read, and the fallback is exactly the path an attacker will aim for.
Step 2 · Draw the failures first
Unusually for a diagram, start from the bottom. Write down every way this can fail before you draw a single successful arrow:
- wrong code, retries remaining - wrong code, retries exhausted - correct code, but too late (outside the window) - correct code, but already used - correct password, wrong username — does the response differ? It must not - the code never arrived (SMS only) - the user no longer has the factor at all
Now draw the happy path, and hang each failure off the arrow where it is detected. Doing it in this order matters: build the success path first and the failures get bolted on wherever there is room, which produces a diagram that is technically complete and useless for finding the gap. Build the failures first and the gap is the branch you could not attach anywhere.
The fifth item is the one that catches people. If a wrong username and a wrong password produce visibly different responses — different text, different status, noticeably different timing — the login page is a username oracle, and the 2FA step is where this most often leaks, because it is tempting to say "this account requires a code" before the password is even checked.
Step 3 · Draw it
Describe it in sentences and let text2diagram lay it out — "after the password is verified the server returns a pre-auth token valid five minutes, the user reads a code from their authenticator app and submits it, the server checks it against the shared secret, marks it consumed and issues a session; after five wrong codes the pre-auth token is burned" comes back as an editable sequenceDiagram with the alt blocks already in place.
Or open one of the three below and rename the participants. The branch structure is the part worth keeping.
2FA flow diagram examples
Three diagrams: the common case done properly, the one with a third party in it, and the one that has to happen before either can.
1 · TOTP at sign-in, with lockout
Three details here are worth copying. The pre_auth_token with its five-minute life is the half-authenticated state from the list above, made concrete. mark this code consumed is the replay guard — one self-arrow, easy to leave out, and its absence means a shoulder-surfed code works for the rest of its window.
The third is the first branch: when the password is wrong, the response says nothing about whether 2FA is enabled. That has to be true even though the server already knows, and it is the reason the branch sits above the pre-auth token rather than after it. Diagrams that check 2FA status first are documenting an account-enumeration endpoint.
Note also that the authenticator app never has an arrow to the server. Both sides compute the same six digits from a secret they agreed on at enrollment, and never speak again.
View the Mermaid source
sequenceDiagram
autonumber
participant User
participant App as Web App
participant Auth as Auth Service
participant TOTP as Authenticator App
User->>App: username and password
App->>Auth: verify credentials
alt password wrong
Auth-->>App: 401 - never reveal whether 2fa is on
App->>User: generic sign in failed
else password correct, 2fa enabled
Auth-->>App: pre_auth_token, valid 5 minutes
App->>User: enter the 6 digit code
User->>TOTP: read the current code
TOTP-->>User: 123456
User->>App: 123456
App->>Auth: verify code with pre_auth_token
alt code matches inside the time window
Auth->>Auth: mark this code consumed
Auth-->>App: full session
App->>User: signed in
else wrong code, attempts left
Auth-->>App: 401 with attempts remaining
App->>User: try again
else 5 failed attempts
Auth->>Auth: burn the pre_auth_token
Auth-->>App: 429 start over from the password
end
end2 · SMS code, including the delivery that never happens
The extra participant changes the diagram in a way people underestimate. The gateway accepting your request (queued) is not the same event as the phone receiving the message, so there are two arrows, and the second one you cannot observe. That gap is why "resend" exists as a feature and why it needs a rate limit — drawn here as a note rather than a branch, because it governs the whole interaction rather than one step.
The failure branch is the one worth arguing about in review: when the gateway is down, does the user get a backup method or a dead end? Every SMS 2FA system needs an answer, and a surprising number of them answer "dead end" simply because nobody drew this arrow.
View the Mermaid source
sequenceDiagram
autonumber
participant User
participant App as Web App
participant Auth as Auth Service
participant SMS as SMS Gateway
participant Phone as User Phone
App->>Auth: password verified, send a code
Auth->>Auth: generate 6 digits, store the hash, ttl 5 min
Auth->>SMS: send to the stored number
alt gateway accepted it
SMS-->>Auth: queued
SMS->>Phone: your code is 123456
User->>App: 123456
App->>Auth: verify the code
Auth-->>App: full session
else gateway rejected or timed out
SMS--xAuth: delivery failed
Auth-->>App: 502 could not send
App->>User: offer a backup method
end
Note over App,Auth: resend is rate limited - 1 per minute, 3 per hour3 · Enrollment — turning 2FA on
The flow everyone implements second and draws never. Two things in it are load-bearing.
The secret stays pending until a code proves it works. Activating 2FA the moment the QR code is displayed locks out every user who closes the tab before scanning — a self-inflicted support queue. The status pending on that self-arrow and the rejection branch at the bottom are the same decision seen from two sides.
Recovery codes are minted here or nowhere. This is the only moment in the entire system where the user is both authenticated and not yet dependent on the new factor, which makes it the only safe place to hand out the way back in. Note shown once — they are stored hashed, exactly like passwords, so this screen genuinely cannot be re-rendered later.
View the Mermaid source
sequenceDiagram
autonumber
participant User
participant App as Web App
participant Auth as Auth Service
participant TOTP as Authenticator App
User->>App: turn on two factor auth
App->>Auth: start enrollment
Auth->>Auth: generate a shared secret, status pending
Auth-->>App: secret as a QR code
App->>User: scan this code
User->>TOTP: scan
TOTP-->>User: 654321
User->>App: 654321
App->>Auth: confirm enrollment with that code
alt code matches the pending secret
Auth->>Auth: activate 2fa, mint 10 recovery codes
Auth-->>App: recovery codes, shown once
App->>User: save these somewhere offline
else code does not match
Auth-->>App: 400 - secret stays pending, 2fa still off
App->>User: scan again
endRead only the half-authenticated span, and ask what it can do
Take the finished diagram and cover everything except the arrows between "password ok" and "session issued". In that span the user holds a credential that is not a session. Now ask three questions: can that credential reach any endpoint other than the verify one? Does it survive longer than the code it is waiting for? Can a second one be minted while the first is alive?
The answers should be no, no, and no. When they are not, the bug is a real one — an account where the password alone is sufficient if you call the right endpoint — and it is invisible in code review because the pre-auth token and the endpoints it can reach are usually in different files. On the diagram it is one covered rectangle.
FAQ
What is the difference between 2FA and MFA?
2FA is exactly two factors; MFA is two or more. So every 2FA setup is MFA, but not the reverse. The factors are supposed to come from different categories — something you know (password), something you have (phone, hardware key), something you are (fingerprint). Two passwords is not 2FA, and neither is a password plus a security question: both are things you know, so one leak of the same kind compromises both. In a diagram the distinction shows up as where the second value comes from — if both arrive from the same participant along the same path, you have not drawn two factors.
What happens if the user loses their phone?
That is the branch your diagram needs to answer, and the answer has to be designed before launch, not improvised during an incident. The three real options are: recovery codes issued at enrollment (drawn in the third example above), a second registered factor so losing one is survivable, or an identity-verified support process — which is a human flow, and worth drawing too, because it is also the flow a social engineer will target. Pick at least one. Systems where the answer is "contact support" without a defined verification procedure are systems where the second factor is whoever sounds most confident on the phone.
Is SMS 2FA still worth drawing, or is it obsolete?
Still worth drawing, because you probably still have it. SMS is the weakest common second factor — SIM swap and SS7 interception both bypass it, and neither requires touching the user's device — but it is also the one with the highest completion rate, because it needs no app. The usual position is: keep SMS for general accounts, require a stronger factor for admin and payment actions. Whatever you choose, draw it, because "SMS is allowed here but not there" is a rule that only exists in a diagram or in someone's head.
How many failed attempts should the diagram allow?
Five is a common choice for a 6-digit code, but the number matters less than what the counter is attached to. Count against the pre-auth token, not the account: counting against the account lets anyone who knows a username lock that user out at will, turning your security control into a denial-of-service tool. Burning the pre-auth token instead just sends the real user back to the password screen, which costs them ten seconds and costs an attacker the whole attempt. Put both the number and what it counts on the diagram.
Should enrollment and verification be one diagram or two?
Two. They happen months apart, they have different participants in practice (enrollment involves a QR code and recovery codes that never appear at sign-in), and the failure modes do not overlap at all — a failed enrollment means 2FA stays off, a failed verification means someone is locked out. Combining them produces a diagram with two entry points, which readers will misread as one flow with an optional beginning. The examples above keep them apart for exactly this reason.
Is it free?
Yes. Anonymous users get 20 generations per day, logged-in users 500. Opening any example on this page in the editor costs nothing — that path does not call the model at all.