Skip to content

Choosing an identity model

Three models, each seen on its own. Put them next to each other and what they're optimising for becomes clear, because each one gives something up to get it.

What each one is really buying

  • Stateful sessions anchor everything on the server. Identity lives there and so does control, so they're predictable, updatable and instantly revocable. In exchange you get a central thing that has to scale and stay in sync, which gets harder with every server you add.
  • Opaque bearer tokens point at the server. Identity still lives there, in a small token-to-user mapping instead of a full session store. The server gets much lighter without becoming free, since there's still a lookup. A practical halfway house.
  • JWTs take the stateless idea to its conclusion, putting the whole identity inside the token. No lookup, no shared store, no coordination, no server load. A signed object the server trusts instantly, and a real fight when you need to revoke access.
  • Delegated identity moves the responsibility outside your system entirely. You stop worrying about passwords, and you inherit the complexity of a relationship with a provider you have to get right.

Stateful is for control. Stateless is for scale. Delegated is for offloading trust.

JunoWhat each one is really buying Nothing here is the best option. Each one is good at something and pays for it somewhere else.

Which is oddly freeing: the question stops being "what's the right answer" and becomes "what can this app afford to be bad at".

JunoWhat each one is really buying The four sit on a line, and it's worth seeing it that way. Sessions keep everything server-side; opaque tokens keep a thin mapping; JWTs keep nothing; delegated keeps the whole question somewhere else.

Move along that line and you trade control for scale, one step at a time. Naming which step you're on tends to settle an architecture argument faster than listing features.

JunoWhat each one is really buying The comparison the table cannot make is operational. Sessions need a store that has to stay up, so its availability becomes your login's availability.

JWTs need no store, and they need key management, rotation and a plan for the day a secret leaks. Delegated needs neither, and makes your uptime partly a provider's.

None of that shows up in a feature comparison, and all of it shows up in an incident. Worth asking, for whichever model you pick, what its 3am failure looks like and who gets paged.

Side by side

StatefulOpaque tokenJWTDelegated
Identity livesOn the serverOn the server, thinlyIn the tokenWith the provider
Per requestSession lookupToken lookupSignature checkDepends on what you issue after
Server storageFull session storeSmall mappingNoneNone
Revoke nowYesYesNoPartly theirs
Scales sidewaysNeeds a shared storeNeeds a shared storeFreelyFreely
You give upCoordinationSome coordinationChanging your mindControl
JunoSide by side The revoke row is the one to read first. It's the difference that bites hardest and the one that's hardest to change later.

If you need someone gone right now, the answer is a lookup somewhere. Everything else follows from that.

JunoSide by side Notice that opaque tokens and sessions share a column more often than not. That's the honest picture: opaque tokens are a lighter version of the same idea, not a different family.

The real fork is between "the server can look it up" and "the server cannot", and the JWT column is the only one on the far side of it.

JunoSide by side Nothing here is fixed forever, and migrating is easier in one direction than the other. Sessions to tokens is mostly additive: issue tokens alongside, move clients, retire sessions. Tokens back to sessions means building the store you deliberately avoided and re-authenticating everyone.

So when the choice is close, the model that keeps a lookup is the cheaper mistake. You can always take state out later; putting it back is the migration nobody schedules.

When each one fits

  • Stateful shines when the server has to stay in charge: instant logout, live role updates, tight control over user state. Stable and predictable, and it suits dashboards, admin tools and anything where control matters more than horizontal scale.
  • Stateless shines when the architecture needs room to stretch. For an API, or anything scaling without coordinated session state, having the client bring its own token removes that friction immediately. Opaque tokens keep it simple; JWTs remove the lookup entirely.
  • Delegated makes sense the moment you realise you don't want to run a password system at all. Or when onboarding speed matters, or when you want a stronger baseline than you'd build yourself. Your app shifts from proving identity to consuming it.

And the shift worth ending on: you often don't pick one. Real systems combine them, using delegated identity to establish who someone is, a session for the main website, and JWTs for API calls. Each solves a different slice of the problem.

JunoWhen each one fits Combining them sounds like extra work and it's usually less. Each model handles the part it's good at.

Google proves who you are, a session keeps you logged into the site, a token gets you through the API. Three jobs, three tools.

JunoWhen each one fits When you combine them, write down where identity is established and where it's consumed, because that's the diagram people argue past each other without.

The common shape: the provider authenticates, your back end issues its own session or token, and everything downstream trusts yours rather than theirs. Continuing to pass the provider's token around is where integrations get tangled.

JunoWhen each one fits One caution on combining: each model you add is a way in, and they need to agree. An account reachable by both a password and a delegated login has two authentication strengths, and an attacker uses the weaker one.

Enforcing MFA on the password path while a delegated path bypasses it is the version of this that ships. So is a password reset flow that hands out a session for an account which only ever logged in through Google.

The rule that holds: every path into an account should be as strong as the strongest thing protecting it, or the strong one is decoration.

Try it

Five systems. Pick a model for each and name the property that decides it.

  1. A classic web dashboard for internal staff. Admins need instant logout, permission changes should apply immediately, and security matters more than scale.
  2. A public API serving a mobile app.
  3. A large microservice architecture where a dozen services each need to know who is calling.
  4. A consumer app whose whole goal is fast signup, ideally with Google or Apple.
  5. A single-server hobby project.
Compare your answers
#ModelThe deciding property
1StatefulThe server stays in charge. Change a role or log someone out and it takes effect on the next request. For a normal browser app, sessions are simple and reliable
2Stateless, either kindEach request brings a token the server checks and moves on, with no growing pile of sessions to track as usage climbs
3Stateless, specifically JWTsSelf-contained, so each service verifies for itself instead of every service asking a central server on every call
4DelegatedNo passwords to handle at all, and the fastest possible signup
5StatefulSimplest thing that works. One server, a small session table, one cookie. No tokens, no OAuth, nothing to operate

One and five reach the same answer from opposite directions, which is the useful part. One picks sessions because control matters most; five picks them because nothing else is needed. Neither is a scaling decision, and scaling is what people assume drives this choice.

Where this goes next

Four questions now travel with you into any login system you meet. Where does identity actually live? How does the server know this person is who they claim? What happens if this leaks, expires or gets tampered with? Which model fits this app, and why?

That's the reasoning, not a checklist, and it's what makes an unfamiliar auth system readable.

The next section changes subject entirely. Rate limiting basics stops asking who someone is and starts asking how often they're allowed to ask.