Skip to content

[Security]: Refresh token rotation has no reuse-detection grace window, causing concurrent refreshes to lock users out #374

Description

@Srejoye

Bug Description

refreshToken (backend/controllers/authController.js) validates the incoming refresh token against the current stored hash, then unconditionally overwrites user.refreshTokenHash with a newly rotated hash. There is no optimistic concurrency check (no compare-and-swap) tying the write to the specific old hash value that was validated, so concurrent refresh calls using the same token interfere with each other.

Steps to Reproduce

  1. Log in and obtain a valid refreshToken.
  2. Fire two concurrent POST requests to /api/auth/refresh using the same refreshToken (e.g. simulating multiple tabs or a double-fired axios interceptor).
  3. Attempt to use both returned refreshTokens.

Expected Behavior

Concurrent legitimate refresh attempts using the same valid token should be handled gracefully (e.g. one succeeds cleanly, or both receive a consistent, valid rotated token), without forcing an unrelated re-login, and any true token-reuse-after-rotation should be distinguishable and trigger revocation.

Actual Behavior

Both requests pass bcrypt.compare against the same pre-rotation hash, then both rewrite refreshTokenHash — whichever save() lands last wins, silently invalidating the other request's newly issued refresh token. The losing client is forced into a full re-login with no distinct error from an actual stolen-token scenario.

Severity

Critical

Screenshots / Screen Recording

No response

Browser

No response

Operating System

No response

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions