Threat Modeling a Real App
One login flow, walked end to end, with the threats written on every arrow.
What you will learn
- -Walk a real login flow arrow by arrow across trust boundaries
- -Apply STRIDE at each crossing to produce concrete threats
- -Prove the method finds flaws a code review would miss
Login looks boring, which is exactly why it is the flow that gets breached. Everyone has seen a hundred login forms, so nobody looks hard at their own. Walk one arrow by arrow with STRIDE in hand and the boring flow turns into a list of ways in.
The : a browser posts email and to an , the server checks them against a users table in Postgres, and on success it issues a session the browser stores in a . Three components, two trust boundaries, roughly five flows. Let us walk it.
Boundary one: browser to API
The cross from fully attacker-controlled space into your server. This is the arrow with the most on it, so slow down here.
Spoofing. Can someone submit credentials that are not theirs. Credential stuffing from a breach dump is the obvious one, so this arrow needs rate limiting and lockout, and ideally behind it. No throttle means an attacker gets unlimited guesses, and unlimited guesses beat any password eventually.
Tampering and information disclosure ride together on the transport. If this is not , the password moves in cleartext and anyone on the path reads it. Force , set HSTS, and never the , because a password in an access log is a breach with a timestamp.
Denial of service. If password verification runs bcrypt at a high work factor with no rate limit, an attacker floods the login and pins your CPU garbage. The mitigation and the spoofing mitigation are the same : rate limit before you hash.
The response is a boundary too
Watch what comes back when auth fails, because the error message is a data flow crossing back to the attacker. "No account with that email" versus "wrong password" tells an attacker which emails are real. That is information disclosure through a username oracle, and it feeds the credential-stuffing threat from the last section. Return one generic "invalid email or password" for both cases.
$Same failure message, same response time, whether the email exists or not.Boundary two: API to database
The lookup query crosses into the data store. Tampering shows up as if the email is concatenated into the query instead of parameterized, so a login form becomes a database dump. Use bound and the threat evaporates.
Information disclosure lives in the users table itself. If passwords sit there in or behind fast , one read of that store hands over every account. Store them with bcrypt, scrypt, or Argon2, so a stolen table is expensive to crack instead of instant.
Token issuance: the flaw code review misses
Success mints a session token, and this is where the password-reset disaster from lesson one hides. If the token is predictable, a sequential ID, a timestamp, the user's email encoded, then elevation of privilege is trivial: forge a token, become another user. The code that builds it can be flawless. The design is the hole.
The fix is a token with real , at least 128 bits from a CSRNG, or a properly signed with the actually verified on every request. Then look at where the browser keeps it. A cookie without HttpOnly is readable by any , without Secure it leaks over , and without SameSite it rides along on cross-site requests. Three flags, and each one closes a distinct threat.
None of these are typos a linter flags. They are decisions, which is the entire point of doing this at the design table.
This walkthrough is how you probe a login flow for weaknesses, and that same walk against a system you have no clearance to test is reconnaissance for an attack. Run it on your own app, on a lab like Juice Shop or DVWA, or on a target with a signed engagement in . The method is identical either way; your to touch the target is the line between an assessment and an intrusion.
Your login returns 'no account found' for unknown emails and 'incorrect password' for known ones. Before any password is guessed, what has an attacker already gained?