[Security][Medium] Stale JWT: demoted/deleted users keep access until token expiry #12

Closed
opened 2026-07-02 01:20:45 +00:00 by wtclaude · 0 comments
Member

Severity: Medium · Type: Security / session

Problem

isLoggedIn (server/src/utils/auth.js:81) trusts id and role straight from the JWT and never re-checks the database. Because the token embeds role and lasts JWT_EXPIRES_IN (default 1d):

  • A user who is demoted from admin keeps their admin token (old role) until it expires.
  • A user who is deleted keeps a working session until the token expires.

This partly undermines the "cannot demote/delete the last admin" guards in admin.controller.js:479,505.

Suggested fix

For sensitive routes, load the user from the DB inside isLoggedIn and use the DB role (or add a token/session version claim and compare it to a stored value, bumping it on role change / password reset / delete). The /auth/me handler already re-fetches from the DB — the authorization path should too.

**Severity:** Medium · **Type:** Security / session ## Problem `isLoggedIn` (`server/src/utils/auth.js:81`) trusts `id` and `role` straight from the JWT and never re-checks the database. Because the token embeds `role` and lasts `JWT_EXPIRES_IN` (default `1d`): - A user who is **demoted** from admin keeps their admin token (old role) until it expires. - A user who is **deleted** keeps a working session until the token expires. This partly undermines the "cannot demote/delete the last admin" guards in `admin.controller.js:479,505`. ## Suggested fix For sensitive routes, load the user from the DB inside `isLoggedIn` and use the DB `role` (or add a token/session version claim and compare it to a stored value, bumping it on role change / password reset / delete). The `/auth/me` handler already re-fetches from the DB — the authorization path should too.
Sign in to join this conversation.
No description provided.