Auth security
Auth code is reviewed by the person who wrote it and trusted by everyone else. This procedure
looks for the specific ways it fails: identity taken from the wrong place, a session that outlives
the reason it was issued, and a recovery flow that is a second, weaker login nobody audited.
When this fires
A credential-handling path is being reviewed, changed, or doubted — login, logout, refresh,
session storage, tokens, reset, invite, impersonation, role change, MFA. Also fires on a report of
one account reaching another's data. It does not fire when the mechanism is still being designed,
or when the question is what a caller may do rather than whether the system knows who they are.
Procedure
- Confirm what you are allowed to touch, before touching it. Name the environment and get it
agreed. Reading code needs no permission; sending crafted requests does. Testing against
production, another tenant, or anyone's real account stops and asks — every time, including
when the fix looks obvious.
- Map the surface. Enumerate every route that issues, accepts, refreshes or revokes a
credential, and every place identity is derived from a request. Grep for session middleware,
token verification,
reset, invite, impersonate, switch_user, admin guards. The list is
the first artifact; a path nobody listed is the one nobody checked.
- Check where identity comes from, per endpoint. The subject must be read from the validated
credential, never from a body field, query parameter, path segment or header the caller
controls. A
user_id in the request that is used rather than compared is the whole bug.
- Walk the session lifecycle. Is the session identifier rotated at login and at every
privilege change, including entering and leaving impersonation — a session that survives login
unchanged is fixation. Does logout invalidate server-side state or only clear the cookie. Are
sessions revoked on password change, reset and MFA enrollment. Is there a way to list and kill
a user's sessions at all.
- Read the real headers, not the config. Observe an actual
Set-Cookie: HttpOnly, Secure,
SameSite, and the narrowest path and domain that works. Check where a token is stored on the
client, and that no credential ever appears in a URL, a redirect target, a referrer or a log.
- Verify the token verification. Signature, algorithm fixed by the server rather than read
from the token, issuer, audience, expiry, and clock skew. Find every call site that decodes
a token where it should verify it — that is the most common token bug in real code. For
stateless tokens, ask what revocation means; if the answer is "it expires eventually", say so
plainly and size the window.
- Test escalation with two real accounts in the authorized environment. Horizontal: replay
one account's request with the other's credential and an object id it does not own. Vertical:
send the privileged request with the unprivileged credential; try setting a role, tenant or
flag field the client should not control; call the admin route directly rather than through
the navigation that hides it.
- Take recovery apart. Reset and invite flows are a second login: token entropy, single use,
short expiry, bound to one account, invalidating existing sessions, and no account enumeration
through differing responses, status codes or timing. Check email-change and phone-change flows
the same way — they are account takeover with extra steps.
- Check what protects the credential endpoints from volume — login, reset, refresh, MFA
submission, invite acceptance — and whether lockout can be aimed at a victim as a denial of
service.
- Follow every path that ends in a session and ask whether MFA is enforced on all of them:
recovery, remembered devices, long-lived API tokens, OAuth callbacks, legacy endpoints. A
factor enforced on one path is not enforced.
- Label each finding by how you know it. Read in the code is a hypothesis. A request and its
response is evidence. Do not report the first as the second, and never report a fix as
verified because the reasoning that produced it was sound.
- Fix at the shared enforcement point, then retest. A guard added to the one route in the
report leaves every sibling caller exposed. After fixing, re-run the exact request that
demonstrated the problem and keep both transcripts.
- Stop before anything outward-facing. Revoking live sessions, forcing a password reset for
real users, filing a public issue, or notifying anyone is a decision with the owner, not a
step in this procedure.
Checklist
Failure handling
- No authorized environment exists — do the code review, report every finding as unconfirmed,
and say exactly which request would confirm each. Do not test production to close the gap.
- The escalation attempt returned 200 with an empty body — that is not a pass. Check whether
the object exists at all before concluding the check held; a missing record and a blocked
request look identical.
- A finding will not reproduce — report it as intermittent with what you tried. Auth bugs that
depend on timing, caching or a replica are real and reporting them as fixed is worse than
reporting them as flaky.
- The fix is a framework upgrade or a library swap — that is a migration with its own blast
radius, not a one-line patch. Scope it separately and say what is exposed in the meantime.
- You found a live compromise — stop testing, preserve what you have, and hand it to the owner.
Continuing to probe destroys the evidence an incident response needs.
Evidence to report
The environment tested and the authorization for it. The route inventory. Per finding: the request
sent, the response observed, the account and privilege level used, and the file and line of the
cause. Which findings were executed and which are code-reading only. The retest transcript for
each fix. And the list of paths not tested — the ones you could not reach, and the ones you could
not test without permission you did not have.
1---2name: auth-security3description: Attack and harden an existing auth surface — session fixation and rotation, token verification, horizontal and vertical privilege escalation, password reset and account recovery, MFA bypass. Use when reviewing login, session, token, reset, invite, impersonation or role-elevation code, when someone reports seeing another user's data or an account takeover, or when auth changes are about to ship. Not for designing the login mechanism or permission model in the first place (authentication, authorization), not for infrastructure IAM, and never run against a system you have not been told you may test.4---56# Auth security78Auth code is reviewed by the person who wrote it and trusted by everyone else. This procedure9looks for the specific ways it fails: identity taken from the wrong place, a session that outlives10the reason it was issued, and a recovery flow that is a second, weaker login nobody audited.1112## When this fires1314A credential-handling path is being reviewed, changed, or doubted — login, logout, refresh,15session storage, tokens, reset, invite, impersonation, role change, MFA. Also fires on a report of16one account reaching another's data. It does not fire when the mechanism is still being designed,17or when the question is what a caller may do rather than whether the system knows who they are.1819## Procedure20211. **Confirm what you are allowed to touch, before touching it.** Name the environment and get it22 agreed. Reading code needs no permission; sending crafted requests does. Testing against23 production, another tenant, or anyone's real account stops and asks — every time, including24 when the fix looks obvious.252. **Map the surface.** Enumerate every route that issues, accepts, refreshes or revokes a26 credential, and every place identity is derived from a request. Grep for session middleware,27 token verification, `reset`, `invite`, `impersonate`, `switch_user`, admin guards. The list is28 the first artifact; a path nobody listed is the one nobody checked.293. **Check where identity comes from, per endpoint.** The subject must be read from the validated30 credential, never from a body field, query parameter, path segment or header the caller31 controls. A `user_id` in the request that is used rather than compared is the whole bug.324. **Walk the session lifecycle.** Is the session identifier rotated at login and at every33 privilege change, including entering and leaving impersonation — a session that survives login34 unchanged is fixation. Does logout invalidate server-side state or only clear the cookie. Are35 sessions revoked on password change, reset and MFA enrollment. Is there a way to list and kill36 a user's sessions at all.375. **Read the real headers, not the config.** Observe an actual `Set-Cookie`: HttpOnly, Secure,38 SameSite, and the narrowest path and domain that works. Check where a token is stored on the39 client, and that no credential ever appears in a URL, a redirect target, a referrer or a log.406. **Verify the token verification.** Signature, algorithm fixed by the server rather than read41 from the token, issuer, audience, expiry, and clock skew. Find every call site that *decodes*42 a token where it should *verify* it — that is the most common token bug in real code. For43 stateless tokens, ask what revocation means; if the answer is "it expires eventually", say so44 plainly and size the window.457. **Test escalation with two real accounts** in the authorized environment. Horizontal: replay46 one account's request with the other's credential and an object id it does not own. Vertical:47 send the privileged request with the unprivileged credential; try setting a role, tenant or48 flag field the client should not control; call the admin route directly rather than through49 the navigation that hides it.508. **Take recovery apart.** Reset and invite flows are a second login: token entropy, single use,51 short expiry, bound to one account, invalidating existing sessions, and no account enumeration52 through differing responses, status codes or timing. Check email-change and phone-change flows53 the same way — they are account takeover with extra steps.549. **Check what protects the credential endpoints from volume** — login, reset, refresh, MFA55 submission, invite acceptance — and whether lockout can be aimed at a victim as a denial of56 service.5710. **Follow every path that ends in a session** and ask whether MFA is enforced on all of them:58 recovery, remembered devices, long-lived API tokens, OAuth callbacks, legacy endpoints. A59 factor enforced on one path is not enforced.6011. **Label each finding by how you know it.** Read in the code is a hypothesis. A request and its61 response is evidence. Do not report the first as the second, and never report a fix as62 verified because the reasoning that produced it was sound.6312. **Fix at the shared enforcement point, then retest.** A guard added to the one route in the64 report leaves every sibling caller exposed. After fixing, re-run the exact request that65 demonstrated the problem and keep both transcripts.6613. **Stop before anything outward-facing.** Revoking live sessions, forcing a password reset for67 real users, filing a public issue, or notifying anyone is a decision with the owner, not a68 step in this procedure.6970## Checklist7172- [ ] Authorized environment named and agreed before any crafted request73- [ ] Every credential-issuing, accepting and revoking route enumerated74- [ ] Subject derived from the validated credential on every endpoint checked75- [ ] Session identifier rotation at login and privilege change confirmed76- [ ] Logout, password change and reset shown to invalidate server-side state77- [ ] Actual cookie attributes observed, not read from configuration78- [ ] Every decode-vs-verify call site checked; algorithm fixed server-side79- [ ] Horizontal and vertical escalation attempted with two accounts80- [ ] Reset, invite and email-change flows walked end to end81- [ ] Each finding labelled as read-in-code or executed-and-observed82- [ ] Fixes retested with the original request, not argued8384## Failure handling8586- **No authorized environment exists** — do the code review, report every finding as unconfirmed,87 and say exactly which request would confirm each. Do not test production to close the gap.88- **The escalation attempt returned 200 with an empty body** — that is not a pass. Check whether89 the object exists at all before concluding the check held; a missing record and a blocked90 request look identical.91- **A finding will not reproduce** — report it as intermittent with what you tried. Auth bugs that92 depend on timing, caching or a replica are real and reporting them as fixed is worse than93 reporting them as flaky.94- **The fix is a framework upgrade or a library swap** — that is a migration with its own blast95 radius, not a one-line patch. Scope it separately and say what is exposed in the meantime.96- **You found a live compromise** — stop testing, preserve what you have, and hand it to the owner.97 Continuing to probe destroys the evidence an incident response needs.9899## Evidence to report100101The environment tested and the authorization for it. The route inventory. Per finding: the request102sent, the response observed, the account and privilege level used, and the file and line of the103cause. Which findings were executed and which are code-reading only. The retest transcript for104each fix. And the list of paths not tested — the ones you could not reach, and the ones you could105not test without permission you did not have.