Threat modeling sounds formal, but the useful version starts with a simple question: what can a user reach that they should not control? For a modern Next.js SaaS, that question crosses the browser, server actions, route handlers, authentication provider, database, billing system, storage, and third-party APIs.
Map the valuable things
Start with assets, not vulnerabilities. Typical assets include account identity, private records, API keys, billing state, uploaded files, organization roles, and administrative actions. If an asset is not listed, it is easy to forget during review.
Next list the actors: anonymous visitor, authenticated user, organization member, owner, administrator, internal service, and external provider. The interesting bugs often appear when one actor can behave like another.
Draw trust boundaries
The browser is not trusted. Values from forms, cookies, URL parameters, local storage, and hidden fields must be treated as attacker-controlled. Server components do not automatically solve authorization; the data operation still needs to verify identity and ownership.
Every integration creates another boundary. Stripe webhooks must be verified. Object storage needs access rules. OAuth callbacks need state validation. Background jobs should not accept an object identifier and assume the requester owned it earlier.
Turn the model into tests
For each sensitive action, write abuse cases:
- repeat the request without a session;
- replace the object identifier with another user's object;
- lower the user's role and retry;
- replay the request;
- alter client-controlled price or status fields;
- trigger the action through the API instead of the interface.
A threat model is useful when it changes implementation or testing. It does not need a hundred-page document. A one-page map of assets, boundaries, and highest-risk flows can prevent more real problems than a generic scanner report produced after launch.