12 Mar 20265 min readAuth · NestJS · Security
Role-aware auth that doesn’t fall apart
“Logged in” is not a permission model. Here’s how I think about OTP, JWT sessions, and multi-actor APIs when the product actually has to ship.
I used to treat auth as a checkbox. User has a token? Cool, they’re in.
That works until your API serves more than one kind of human — admin, doctor, patient, operator — and suddenly “logged in” means almost nothing. The interesting part of auth is not issuing JWTs. It’s deciding who this person is allowed to be, right now, on this route.
Start with actors, not libraries
The first design question is simple: who can do what? Role should be decided at login and stay fixed for that session. If someone verified as a patient, that OTP should not quietly become a doctor session. Status matters too — a suspended account can still hold a valid JWT until you kill the session on purpose.
This is why I separate identity from authorization. The token answers “who are you?” Guards and policies answer “are you allowed?” Mixing those two is how APIs get soft and scary.
Three thin layers beat one fat middleware
In NestJS I like three readable checks. First: is this a real session? Second: is this role allowed on this route group? Third: does domain state still allow them to act — for example a doctor who got suspended mid-day?
Each layer fails closed. You can read the stack in five seconds, which is exactly what you want at 1 a.m. when something is wrong. One giant auth middleware that “does everything” becomes unmaintainable the week a new actor appears.
Give yourself an off switch
Short access tokens, rotating refresh tokens, and a session store you control turn logout and suspend into real operations — not “wait for the JWT to expire.” Rate-limit OTP by phone and IP. Return errors that mean something: role mismatch is not the same as expired token.
The traps I keep seeing: permissions only in the frontend, one shared user token across apps with different trust levels, and internal jobs treated as safe because they live on a private network. Auth is a product decision. The code just has to respect it.
If your auth model can’t explain who is allowed to do what in one sentence, the implementation will eventually invent its own answers — usually at the worst possible time.
Further reading
OWASP Authentication Cheat Sheet
Practical baseline for login, sessions, and common failure modes.
NestJS Authentication docs
Guards, strategies, and how Nest expects auth to be composed.
RFC 9700 — OAuth 2.0 Security Best Current Practice
Modern guidance if your auth story expands beyond simple JWT sessions.
Keep reading