[SECURITY AUDIT] Session/token revocation is a non-functional stub — logout and password change do not invalidate existing JWTs #30
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Medium
Confidence: High
Scope area: Session management & token expiry (audit area 4)
What
Sessions are stateless JWTs and there is no server-side session store or denylist. The revocation hooks in
server/src/auth/session.service.js(revokeSession,invalidateSession,invalidateAllUserSessions, lines ~187-200) are stubs that only log and returntrue. As a result:logoutinserver/src/router/v1/auth/auth.controller.js:99-102callsclearAuthCookie— it clears the cookie in the caller's browser but does nothing to the token itself. A copy of the cookie value (e.g. captured from a shared/compromised machine, a proxy log, or an XSS-exfiltrated value) remains a fully valid session until the JWT's natural expiry (JWT_EXPIRES_IN, default 1 day).updateUser/users.update(server/src/router/v1/admin/admin.controller.js:505,server/src/model/users/users.model.js:55) rehash the password but never call any invalidation. An attacker with a stolen token keeps access for up to a day even after the victim rotates their password — the usual "change password to kick everyone out" expectation silently fails.requireAuthre-reads the user row on each request, so a deleted or role-demoted user does lose access promptly — but an intact user with a stolen still-valid token does not, and there is no way to force-expire it.Why it matters
Token theft has no containment path. The two standard mitigations users expect — "log out everywhere" and "change password to revoke sessions" — are both no-ops here. The mobile bearer flow got this right (opaque, DB-stored, revocable refresh tokens); the web/cookie session flow has no equivalent.
Where
server/src/auth/session.service.js:182-200(stub revocation)server/src/router/v1/auth/auth.controller.js:99-102(logout)server/src/auth/token.js:16(JWT_EXPIRES_INdefault1d)server/src/auth/session.middleware.js:35-49(requireAuth— DB revalidation covers deleted/demoted but not token revocation)Suggested fix (not implemented)
Introduce a minimal server-side session/denylist keyed on the JWT
jti(already minted per session increateSession), or a per-usertoken_valid_aftertimestamp column checked inrequireAuth. On logout, add thejtito the denylist; on password change, bumptoken_valid_afterso all older tokens fail validation. ShortenJWT_EXPIRES_INas an interim mitigation. The existingjticlaim and theinvalidate*hook points are already in place for this.