Skip to content

flask: Invalidate sessions and JWTs when a user's password changes (1/3 split of #73) - #74

Open
RudraBJoshi wants to merge 1 commit into
Open-Coding-Society:mainfrom
dhyantsoni:split/flask/f1-jwt-session-invalidation
Open

flask: Invalidate sessions and JWTs when a user's password changes (1/3 split of #73)#74
RudraBJoshi wants to merge 1 commit into
Open-Coding-Society:mainfrom
dhyantsoni:split/flask/f1-jwt-session-invalidation

Conversation

@RudraBJoshi

Copy link
Copy Markdown

Splitting #73 into smaller, independently-reviewable PRs across spring/flask/pages. This one covers the password reset button side of flask — session/JWT invalidation on password change.

Independent of the other PRs in this stack.

Previously nothing tied an issued JWT or Flask-Login session to a specific password: the JWT had no exp claim at all and carried nothing password-derived, and sessions carried a bare user id. A stolen JWT or session cookie kept working indefinitely, surviving a password reset meant to lock an attacker out.

Adds User.token_version, bumped in set_password() only on an actual hash change. JWTs now carry token_version + exp and are checked against the account's current value; sessions carry it via a composite get_id(), checked in load_user.

Requires a schema change before deploy: ALTER TABLE users ADD COLUMN token_version INTEGER NOT NULL DEFAULT 0;

Original PR: #73

*** REQUIRED BEFORE THIS DEPLOYS: production runs MySQL (see __init__.py --
SQLALCHEMY_DATABASE_URI switches to MySQL whenever DB_ENDPOINT/DB_USERNAME/
DB_PASSWORD are set), a completely separate database this session had no
access to. Only the local dev SQLite DB has been migrated. Someone MUST run
this against production before/with this deploy, or every login there will
error on the missing column:

    ALTER TABLE users ADD COLUMN token_version INTEGER NOT NULL DEFAULT 0;

***

Previously nothing tied an issued JWT or Flask-Login session to a specific
password: the JWT carried no exp claim at all (never expired by JWT
semantics) and no password-derived data, and Flask-Login sessions just
carried a bare user id, re-validated against fresh DB data on every request
but with no check that the underlying credential hadn't changed. A stolen
JWT or session cookie kept working indefinitely, surviving a password reset
that was meant to lock an attacker out.

Adds User.token_version, bumped in set_password() (the single funnel every
password-change path already goes through) only on an actual hash change.
JWTs now carry token_version + exp and are checked against the account's
current value in auth_required. Sessions now carry it via a composite
get_id() ("id:token_version"), checked in load_user (main.py), so a stale
session is rejected before ever reaching a @login_required route instead of
running with outdated auth state.

Verified live: fresh JWT/session -> 200, password reset -> old JWT gets 401
with an explicit "password has changed" message, old session gets redirected
to login, fresh login after the reset works again.
@RudraBJoshi

RudraBJoshi commented Aug 26, 2026

Copy link
Copy Markdown
Author

@jmort1021 accept the pull request pls so Dhyan can add the schema

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant