Login Flow Diagram Guide
How to draw a login flow diagram that answers the questions reviewers actually ask — what a session is, why every failure looks the same, and three editable examples.
What is a login flow diagram?
A login flow diagram shows how an anonymous request turns into an identified one. Not "how the password is checked" — that part is one arrow and a library call. What the diagram is actually about is the thing handed back afterwards, how long it lives, where it is stored, and what invalidates it.
Almost every login diagram on the internet is the same picture: a box for the form, a diamond that says credentials correct?, a green arrow to "Dashboard" and a red arrow back to "Login". That picture is not wrong, it is just answering a question nobody has. Nobody is confused about whether a wrong password should let you in. What people are confused about — and what breaks in production — is everything the diamond leaves out: what got created on the yes branch, how many times you can take the no branch before something happens, and whether the two branches are distinguishable from the outside.
That last point is why this is usually a sequence diagram rather than a flowchart. A flowchart draws your server's reasoning. A sequence diagram draws what crosses the wire, which is the only thing an attacker can see, and therefore the only thing worth reviewing. The moment you draw responses as arrows instead of outcomes as boxes, "user not found" and "wrong password" stop being two branches of your logic and become two strings a stranger can tell apart.
This page draws the password path. The second factor and the sign-in-with-Google button attach to it at a single point each, and each has its own page — folding them in here would produce a diagram with three beginnings.
What a login flow diagram has to show
Six things. If your diagram has the diamond but not these, it is a picture of a form, not of a login.
- The session, as a named artifact with a lifetime. Not "log the user in" — draw what is created, where it is stored, and for how long:
sidcookie,HttpOnly Secure SameSite=Lax, 30-day absolute lifetime. Half the arguments a login design produces are actually arguments about cookie attributes that nobody wrote down. - One response for every failure. Wrong password, unknown email, disabled account, unverified email — the arrow back to the user should carry the same status and the same words. Draw them merging into a single arrow, because a diagram with four differently-worded error arrows is the enumeration bug, drawn.
- The rate limit, with its two subjects. Per account and per IP are different controls solving different attacks — credential stuffing against one user, spraying across many. Put both on the diagram with numbers, and put the lockout response on it too, since "429 with a retry-after" and "silently pretend the password was wrong" are different products.
- The constant-time detail. The hash verification runs even when there is no user row. If your diagram branches to "return 401" before the hash step, it is documenting a timing oracle: the unknown-email path returns in 2ms and the wrong-password path in 200ms, and that difference is machine-readable.
- The two attachment points, as one arrow each. The second factor plugs in after the password check and before the session is issued. An external identity provider replaces the password check entirely. Marking those two spots keeps this diagram honest about its own scope, and tells the reader where the other diagram begins.
- Password reset. It is not a support feature, it is a second way to obtain a session, and it is attacked as one. A login diagram that stops at the form is missing half of its own attack surface.
Here is the skeleton — the version everybody draws, with none of the six on it. It is worth looking at precisely because it looks finished. Every arrow in it is correct; the diagram is still useless for review, because nothing here can be wrong in an interesting way.
View the Mermaid source
sequenceDiagram
participant User
participant App as Web App
participant Auth as Auth Service
participant DB as User Store
User->>App: email and password
App->>Auth: sign in
Auth->>DB: look up the user by email
DB-->>Auth: the stored password hash
Auth-->>App: session token
App->>User: redirect to the dashboardHow to draw a login flow diagram
Three steps. The first one is a reordering, and it is most of the value.
Step 1 · Draw the session first, not the check
Start at the end. Before you draw a single arrow of the form, write down what exists after a successful login that did not exist before:
- a row somewhere, or a signed token with nothing behind it - an identifier delivered to the browser, with a specific set of cookie attributes - an expiry — and probably two: an idle one and an absolute one - a list of things that destroy it early
Now work backwards. The credential check exists to authorise creating that object, and once you see it in that order the diagram writes itself: the check is a guard, and everything interesting is on the other side of it.
Doing it the usual way — form, diamond, done — produces diagrams where the session is implied by an arrow labelled "success". That label is where lifetime bugs hide, because nothing on the page ever forced anyone to say how long "success" lasts.
Step 2 · Make every failure look identical
List every way this can go wrong, then draw them all arriving at one arrow:
- no account with that email - account exists, wrong password - account exists, correct password, but disabled or unverified - too many attempts
Only the last one is allowed to look different, and only because it has to — you cannot rate-limit someone without telling them. Everything else gets the same status code, the same message, and ideally the same latency.
This is the step people skip, and skipping it is not a subtle mistake. A signup form that says "this email is already registered" combined with a login that says "no account with that email" is a free membership-checking API: feed it a leaked address list and it tells you who has an account here. Both messages are individually helpful and jointly a disclosure, which is exactly the kind of thing a diagram catches and a code review does not, because the two strings live in different files written months apart.
If your product genuinely needs to tell users their account is disabled, do it after the password is verified — at that point you are talking to the account owner, not to a stranger. On the diagram that is one arrow moved below a branch, and it is the whole fix.
Step 3 · Draw it
Describe it in sentences and let text2diagram lay it out — "the user posts an email and password, the service rate limits by email and by IP, loads the user, verifies the hash even when no user exists, and on success creates a 30-day session and sets an HttpOnly cookie; every failure returns the same 401" comes back as an editable sequenceDiagram with the alt blocks already nested correctly.
Or open one of the three below and rename the participants. The branch structure is the part worth keeping.
Login flow diagram examples
Three diagrams covering the three questions a login design actually has to answer: what happens at the door, what the thing you were given is worth over time, and how someone gets back in without it.
1 · Password sign-in, with the parts people leave out
Four details here are the reason to copy this one rather than draw your own from memory.
The rate limit is before the database lookup, and it names both subjects. Putting it after means an attacker still gets a free query per attempt, which is the expensive part.
run it even when there is no row looks like a note to an implementer, and it is, but it is also the difference between a login endpoint and a user-existence endpoint. Skip it and the timing tells the whole story.
The session arrow carries its attributes and its lifetime. That single label has settled more design arguments than any paragraph of prose next to it.
And the failure branch merges: no row and wrong password produce the same arrow, deliberately drawn as one. If you can find two distinguishable failure arrows in your own diagram, you have found a bug without running anything.
View the Mermaid source
sequenceDiagram
autonumber
participant User
participant App as Web App
participant Auth as Auth Service
participant DB as User Store
User->>App: email and password
App->>Auth: sign in
Auth->>Auth: rate limit check - 5 per email, 20 per ip, 15 min
alt over the limit
Auth-->>App: 429 with retry after
App->>User: too many attempts, try again later
else within the limit
Auth->>DB: load the user by email
DB-->>Auth: a row, or nothing
Auth->>Auth: verify the hash - run it even when there is no row
alt hash matches and the account is active
Auth->>DB: create session, idle 30 min, absolute 30 days
Auth-->>App: Set-Cookie sid, HttpOnly Secure SameSite=Lax
App->>User: redirect to the dashboard
else no row, wrong password, or account disabled
Auth->>Auth: count this attempt against both limits
Auth-->>App: 401 email or password is incorrect
App->>User: one message for every failure above
end
end2 · What the session does after login
A state diagram, because this part is not a conversation — it is one object aging. Drawing it separately is what stops "session" from being a word on an arrow.
The two expiries are the point. Idle is a sliding window that resets on every request; absolute does not reset, ever. Systems with only the sliding one issue sessions that live forever as long as a tab is open somewhere. Systems with only the absolute one log people out mid-task at an arbitrary moment. Almost everyone wants both, and almost nobody writes both down.
Revoked is the transition worth arguing about in review. When a user changes their password because they think someone else has it, the only response that matches their intent is killing every session everywhere — including the attacker's. If your diagram has no arrow from password changed to Revoked, then changing the password does nothing to whoever is already inside.
View the Mermaid source
stateDiagram-v2
[*] --> Anonymous
Anonymous --> Active: password verified, session created
Active --> Active: request inside the idle window
Active --> Idle: 30 minutes with no request
Idle --> Active: a request arrives, window slides
Idle --> Expired: idle window ran out
Active --> Expired: 30 days since it was created
Active --> Revoked: signed out, password changed, or admin action
Expired --> Anonymous
Revoked --> Anonymous
note right of Revoked
a password change revokes every session
including the ones on devices we cannot see
end note3 · Password reset — the other way to get a session
Draw this next to the login diagram, not in a separate document, because it grants exactly what login grants and is usually protected far less carefully.
The first response is the whole design in one arrow: 200 regardless of whether the account exists, sent before anything is looked up. Every other ordering leaks. The user-facing text has to match — "if that email is registered we sent a link" — and yes, it is slightly worse copy. It is also the only wording that does not turn this form into the enumeration endpoint your login page refused to be.
The token is single-use and short-lived, and the reason both matter is that reset links land in mailboxes, which get forwarded, backed up, and synced to devices nobody remembers owning.
The revocation arrow is the one most implementations forget. Someone resetting their password is very often someone who has already been compromised. If old sessions survive the reset, the reset accomplished nothing at all.
View the Mermaid source
sequenceDiagram
autonumber
participant User
participant App as Web App
participant Auth as Auth Service
participant Mail as Email Service
User->>App: I forgot my password, here is my email
App->>Auth: start a reset
Auth-->>App: 200 - the same answer whether or not it exists
App->>User: if that email is registered we sent a link
opt the account actually exists
Auth->>Auth: mint a single use token, valid 30 minutes
Auth->>Mail: send the reset link
Mail->>User: reset link
User->>App: open the link and choose a new password
App->>Auth: redeem the token
alt token unused and still inside the window
Auth->>Auth: store the new hash, burn the token
Auth->>Auth: revoke every existing session
Auth-->>App: done, sign in again
else token used, expired, or unknown
Auth-->>App: 400 ask for a fresh link
end
endRead only the arrows pointing back at the user
Cover everything on the finished diagram except the arrows that go back to the user. Those are the only things an attacker can observe. Now read them as a list, ignoring which branch each came from.
If any two of them differ — different status, different wording, different obvious latency — for reasons the user is not entitled to know, you have found a disclosure. This is a two-minute exercise, it needs no tooling, and it catches the single most common real flaw in login systems, which is not weak hashing or missing HTTPS but a login page that will happily tell a stranger which of your users exist.
FAQ
Should a login flow be a flowchart or a sequence diagram?
Sequence diagram, in almost every case. The decisions in a login are trivial — is this hash equal to that hash — while the interesting content is who talks to whom and what comes back, which is what a sequence diagram is for. Use a flowchart only when the audience is non-technical and the point is the user's journey rather than the system's behaviour, and accept that such a diagram cannot be reviewed for security because it does not draw responses at all. The one place a flowchart genuinely wins is a screen-by-screen map of a multi-step sign-in for a design handoff.
Where do 2FA and social sign-in go on the diagram?
Each attaches at exactly one point, and each deserves its own diagram. 2FA goes between the password check and the session creation — the server returns a short-lived pre-auth token instead of a session, and the real session is issued only after the code is verified. Social sign-in replaces the password check entirely: your service never sees a credential, it receives an identity assertion from a provider and creates a session from it. Mark both spots on your login diagram with a single arrow each and stop there. A combined diagram has three entry points and readers reliably misread it as one flow with optional middles.
Does the diagram change if I use JWTs instead of server-side sessions?
The first example barely changes — one arrow says sign a token instead of create a session row. The second example changes a lot, and that is the honest argument between the two approaches. A stateless token cannot be revoked, so the arrows into
Revokedeither disappear or turn into a blocklist, which is a session store wearing a different hat. Draw it and the trade-off stops being abstract: either sign-out is not real until the token expires, or you have reintroduced the state you were avoiding. The usual resolution — short access tokens plus a revocable refresh token — is worth drawing explicitly, because the refresh exchange is where its real complexity lives.Should the diagram show "user not found" as its own branch?
Internally yes, externally no — and drawing both is the point. Your server does take a different code path, and hiding that makes the diagram a lie. What must be identical is the arrow that leaves your server: same status, same body, same approximate latency. So draw the internal branch, then draw both of its ends converging on one response arrow. A diagram that shows the branches converging is documenting a deliberate decision; a diagram with no branch at all is documenting an implementation nobody has checked.
How long should a session last on the diagram?
Whatever you actually use — the value of writing it down is far higher than the value of picking the right number. As a starting point: 30 minutes idle plus 30 days absolute for consumer products, and much shorter for anything handling money or admin powers, where an idle window under 15 minutes and a re-authentication prompt before sensitive actions are normal. The rule worth following is that the number belongs on the arrow, not in a config file nobody reads. Sessions that outlive their usefulness are the quietest security problem there is, because nothing about them ever fails.
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.