Session is invalidated on password change
Why it matters
When a user changes their password they are often responding to a suspected compromise. Failing to invalidate existing sessions at that moment means an attacker who already has a session token retains access even after the user takes corrective action. CWE-613 and CWE-287 both cover this failure mode, and OWASP ASVS V3.3.1 requires that sessions be terminated on credential change. OWASP A07 lists this as an authentication lifecycle gap. In short: a password change that does not invalidate sessions is not a password change — it is a password addition.
Severity rationale
Medium because exploitation requires an attacker to already possess a valid session token, but that prior access makes this a critical path in incident recovery.
Remediation
Combine the password update and session revocation in a single atomic transaction so neither can succeed without the other:
// src/app/api/account/password/route.ts
async function changePassword(userId: string, newPassword: string) {
const hash = await bcrypt.hash(newPassword, 12)
await db.$transaction([
db.user.update({ where: { id: userId }, data: { passwordHash: hash } }),
db.session.deleteMany({ where: { userId } })
])
}
For JWT-only systems, store a passwordChangedAt timestamp on the user record and add a check in token validation that rejects any JWT with iat < passwordChangedAt.
Detection
-
ID:
session-invalidation-password-change -
Severity:
medium -
What to look for: Find the password change handler (typically in an account settings API route). Check whether it invalidates existing sessions for that user after the password is updated. With database sessions: look for a call to delete/revoke sessions by userId. With JWT sessions: look for a
passwordChangedAttimestamp field on the user record that is compared against the JWT'siatclaim. With managed auth providers (Clerk, Auth0): they handle this automatically. Count all instances found and enumerate each. -
Pass criteria: After a password change, all existing sessions for that user are invalidated (or the JWT validation logic rejects tokens issued before the password change). The current session may be re-established with the new password, but old sessions on other devices should be terminated. At least 1 implementation must be confirmed.
-
Fail criteria: Password change API route updates the password hash but takes no action on existing sessions — an attacker who has compromised a session retains access even after the user resets their password.
-
Skip (N/A) when: No password-based authentication — the project uses social auth only (OAuth only, no local passwords). Signal: no password field in user schema, no password change endpoint.
-
Detail on fail:
"Password change handler at /api/account/password updates password hash but does not revoke existing sessions in the sessions table"or"JWT strategy used with no passwordChangedAt check — stolen tokens remain valid after password reset". -
Remediation: When a user changes their password, they are often doing so because they suspect their account is compromised. Invalidating all sessions is the right response:
// Database sessions approach async function changePassword(userId: string, newPassword: string) { const hash = await bcrypt.hash(newPassword, 12) await db.$transaction([ db.user.update({ where: { id: userId }, data: { passwordHash: hash } }), db.session.deleteMany({ where: { userId } }) // Revoke ALL sessions ]) }For JWT-only systems, store a
passwordChangedAttimestamp and reject any token withiat < passwordChangedAt.
External references
- cwe · CWE-613 — Insufficient Session Expiration
- cwe · CWE-287 — Improper Authentication
- owasp:2021 · A07
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-authentication·automated