Skip to content

Fix: Prevent revoked refresh tokens from bypassing silent refresh#211

Merged
kunalverma2512 merged 4 commits into
kunalverma2512:mainfrom
riya-chauhan12:fix/silent-refresh-revocation
Jun 18, 2026
Merged

Fix: Prevent revoked refresh tokens from bypassing silent refresh#211
kunalverma2512 merged 4 commits into
kunalverma2512:mainfrom
riya-chauhan12:fix/silent-refresh-revocation

Conversation

@riya-chauhan12

@riya-chauhan12 riya-chauhan12 commented Jun 17, 2026

Copy link
Copy Markdown

Fix: Prevent revoked refresh tokens from bypassing silent refresh

Related Issue

Closes #208

Summary

This PR fixes a security issue in the authentication middleware where revoked refresh tokens could still be used during the silent refresh flow.

Problem

The /api/auth/refresh endpoint correctly validates refresh tokens against the stored security.refreshTokenHash before issuing new tokens.

However, the silent refresh logic inside authMiddleware.js only verified the JWT signature using verifyRefreshToken() and did not validate the presented refresh token against the hash stored in the database.

As a result, if an access token expired and a revoked refresh token was still present, the middleware could silently authenticate the request and issue a new access token.

Root Cause

The silent refresh path performed:

  • Refresh token JWT verification
  • User lookup

but skipped:

  • Validation against security.refreshTokenHash

This created inconsistent behavior between:

  • /api/auth/refresh
  • Silent refresh inside authMiddleware

Solution

Added refresh token hash validation to the silent refresh flow.

The middleware now:

  1. Retrieves the user along with security.refreshTokenHash
  2. Verifies that a stored hash exists
  3. Validates the incoming refresh token using bcrypt.compare()
  4. Rejects revoked or invalid sessions with a 401 Unauthorized response

The new access token is only issued when the refresh token successfully passes hash validation.

Verification

To reproduce and validate the issue, I created a local regression test covering the silent refresh flow.

Test Case 1: Revoked Refresh Token

Setup:

  • Created a test user

  • Generated a refresh token

  • Removed security.refreshTokenHash to simulate logout/session revocation

  • Sent a request with:

    • an expired access token
    • the revoked refresh token

Result before fix:

  • Response: 200 OK
  • Silent refresh incorrectly authenticated the request

Result after fix:

  • Response: 401 Unauthorized
  • Revoked session correctly rejected

Test Case 2: Valid Refresh Token

Setup:

  • Created a test user

  • Stored a valid refresh token hash

  • Sent a request with:

    • an expired access token
    • a valid refresh token

Result:

  • Response: 200 OK
  • Silent refresh succeeds as expected

Files Changed

  • server/middlewares/authMiddleware.js

Impact

This change ensures refresh token revocation is consistently enforced across all authentication flows and prevents revoked refresh tokens from being used to regain access through silent refresh.

Summary by CodeRabbit

Bug Fixes

  • Strengthened session token refresh by validating refresh tokens against the stored hash to prevent unauthorized token reissuance
  • Improved silent-refresh failure handling, clearing auth cookies and returning clearer “session expired” guidance to log in again
  • Enhanced authentication state verification to make session management more reliable across the application

@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Kunal Verma's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

Copy link
Copy Markdown

🎉 Welcome to CodeLens — Thank You for Your Contribution!

Hey @riya-chauhan12! 👋

We are genuinely excited to have you here. Every single PR — big or small — makes CodeLens better, and yours is no exception. Take a moment to review the checklist below to help us merge your work quickly and smoothly.

✅ Before Requesting a Review

  • Keep code clean, readable, and consistent with the existing codebase
  • Avoid unrelated or unnecessary file changes
  • Make sure the UI is fully responsive across all device sizes
  • Attach screenshots or a short screen recording for any UI changes
  • Resolve all merge conflicts before marking the PR as ready
  • Do not submit AI-generated, copy-pasted, or low-effort implementations

💬 Join Our Community Channel — This is Mandatory

Being part of our communication channel is compulsory for all contributors, not optional.

📡 Join the CodeLens Matrix Channel

Why join? This is where all important announcements, PR review updates, contribution discussions, and maintainer decisions happen in real time. Contributors who are not in the channel regularly miss critical context and updates, which often leads to duplicated or misaligned work. Staying connected here is what keeps the community strong and your contributions impactful.


We are rooting for you! If you have any questions, drop them in the channel or comment right here on this PR. Let's build something great together. 🚀✨

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@riya-chauhan12, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 40 minutes and 2 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e979c7f-1cc5-44dd-8eda-98e1d393320a

📥 Commits

Reviewing files that changed from the base of the PR and between 4ecdad5 and c9dd2c1.

📒 Files selected for processing (1)
  • server/middlewares/authMiddleware.js
📝 Walkthrough

Walkthrough

The auth middleware imports bcryptjs and rewrites the silent refresh path to load the user's stored security.refreshTokenHash, validate the refresh token cookie against it via bcrypt.compare, regenerate the access token from the user record, and reject revoked or missing sessions with explicit error propagation. The refreshed user is cached locally to avoid a redundant database lookup.

Changes

Silent Refresh Token Revocation Fix

Layer / File(s) Summary
bcryptjs dependency and prefetched user caching
server/middlewares/authMiddleware.js
Adds bcryptjs import and introduces a prefetchedUser variable to cache the user loaded during silent refresh, eliminating a redundant database query.
Silent refresh token validation and access token regeneration
server/middlewares/authMiddleware.js
Restructures silent refresh to fetch security.refreshTokenHash from the user record, run bcrypt.compare against the cookie token, reject missing or revoked sessions, regenerate the access token from stored user fields, rebuild the decoded payload, and propagate ApiError while mapping other failures to session-expired errors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • kunalverma2512/CodeLens#103: Modifies the same authMiddleware.js file to implement refresh-token cookie validation and HttpOnly cookie handling in the silent refresh flow.

Poem

🐇 Hop hop, no sneaky tokens allowed!
The hash must match before we proceed proud.
bcrypt checks the cookie with care,
Revoked refresh tokens? None shall dare.
The middleware guards the burrow tight,
Only valid sessions pass through tonight! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses the core objective from issue #208 by implementing refresh token hash validation in the silent refresh flow, making validation consistent between endpoints. Address critical issues before merging: (1) Import and call clearAuthCookies on both rejection paths before throwing 401, (2) Store the fetched user in a scoped variable to eliminate double DB queries, (3) Fix formatting inconsistencies to match code style.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main security fix: preventing revoked refresh tokens from bypassing silent refresh, which is the core change in the PR.
Description check ✅ Passed The description is comprehensive and covers the issue, solution, test verification, and files changed, but lacks committed test files despite mentioning regression tests were created.
Out of Scope Changes check ✅ Passed All code changes are directly within scope of fixing the security vulnerability by adding hash validation; no unrelated modifications were introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
server/middlewares/authMiddleware.js (2)

71-78: 💤 Low value

Inconsistent formatting reduces readability.

The object literals have irregular indentation and spacing (e.g., refreshUser._id ,email with extra space before comma, inconsistent line breaks). Consider reformatting for consistency with the rest of the codebase.

✨ Suggested formatting
-          const newAccessToken = generateAccessToken({userId: refreshUser._id, email: refreshUser.email, role: refreshUser.role
-          });
-
-          // Set only the new access token cookie
-          setAccessTokenCookie(res, newAccessToken);
-
-          decoded = {userId: refreshUser._id ,email: refreshUser.email,  role: refreshUser.role
-        };
+          const newAccessToken = generateAccessToken({
+            userId: refreshUser._id,
+            email: refreshUser.email,
+            role: refreshUser.role
+          });
+
+          // Set only the new access token cookie
+          setAccessTokenCookie(res, newAccessToken);
+
+          decoded = {
+            userId: refreshUser._id,
+            email: refreshUser.email,
+            role: refreshUser.role
+          };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/middlewares/authMiddleware.js` around lines 71 - 78, The object
literals in the generateAccessToken function call and the decoded variable
assignment have inconsistent formatting with irregular spacing and indentation.
Reformat both the object passed to generateAccessToken and the decoded object to
have consistent spacing around commas and colons, and remove the extra space
before commas (e.g., in refreshUser._id ,email). Ensure the formatting matches
the style conventions used elsewhere in the codebase.

65-70: ⚡ Quick win

Consider clearing the refresh token hash on invalid token to match explicit endpoint behavior.

The explicit /api/auth/refresh endpoint (context snippet 2, lines 163-164) clears security.refreshTokenHash and auth cookies when an invalid token is presented, treating it as potential token theft. This middleware only rejects with 401 but leaves the session intact.

An attacker with a stolen/rotated token could repeatedly probe silent refresh without triggering session invalidation. Aligning behavior would strengthen the defense-in-depth.

🛡️ Suggested enhancement
          if (!isValid) {
+           await User.findByIdAndUpdate(userId, { $unset: { "security.refreshTokenHash": "" } });
            throw new ApiError(
            401,
            "Invalid session. Please log in again."
            );
          }

Note: You may also want to import and call clearAuthCookies(res) before throwing, consistent with the explicit refresh endpoint's defensive behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/middlewares/authMiddleware.js` around lines 65 - 70, In the
authMiddleware where the invalid session check throws the ApiError for "Invalid
session. Please log in again.", you need to align the behavior with the explicit
refresh endpoint by clearing the session state before throwing. Before the throw
statement in the if (!isValid) block, clear the refreshTokenHash from the
security object and call clearAuthCookies(res) to invalidate the auth cookies,
matching the defensive behavior of the /api/auth/refresh endpoint and preventing
repeated probing with stolen tokens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@server/middlewares/authMiddleware.js`:
- Around line 71-78: The object literals in the generateAccessToken function
call and the decoded variable assignment have inconsistent formatting with
irregular spacing and indentation. Reformat both the object passed to
generateAccessToken and the decoded object to have consistent spacing around
commas and colons, and remove the extra space before commas (e.g., in
refreshUser._id ,email). Ensure the formatting matches the style conventions
used elsewhere in the codebase.
- Around line 65-70: In the authMiddleware where the invalid session check
throws the ApiError for "Invalid session. Please log in again.", you need to
align the behavior with the explicit refresh endpoint by clearing the session
state before throwing. Before the throw statement in the if (!isValid) block,
clear the refreshTokenHash from the security object and call
clearAuthCookies(res) to invalidate the auth cookies, matching the defensive
behavior of the /api/auth/refresh endpoint and preventing repeated probing with
stolen tokens.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4000dcac-fa7a-4a40-8065-0c2a12ecc1bf

📥 Commits

Reviewing files that changed from the base of the PR and between a280210 and 5729967.

📒 Files selected for processing (1)
  • server/middlewares/authMiddleware.js

@kunalverma2512

kunalverma2512 commented Jun 17, 2026

Copy link
Copy Markdown
Owner

@coderabbitai please check the work done by @riya-chauhan12
I also wanted that you think about all the discussion done in the issue description : #208 and see is this solution done by @riya-chauhan12 is the best for codebase health and for long term

also check is this fully compatible with our codebase and especially nearby files

I want strict check because this project is deployed on vercel and render so after merging , no error in production must come so check in deep and tell whether this PR is good to good and hence ready for merge and if not then tell in detail what more contributor has to do more ?

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Oops, something went wrong! Please try again later. 🐰 💔

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the PR details and diff
gh pr view 211 --repo kunalverma2512/CodeLens --json title,body,state,files,additions,deletions

Length of output: 3845


🏁 Script executed:

#!/bin/bash
# Get PR diff
gh pr diff 211 --repo kunalverma2512/CodeLens

Length of output: 2893


🏁 Script executed:

#!/bin/bash
# Read the authMiddleware.js
cat server/middlewares/authMiddleware.js

Length of output: 3978


🏁 Script executed:

#!/bin/bash
# Read nearby auth-related files
cat server/routes/auth.js 2>/dev/null || echo "NOT FOUND"
fd -t f "auth" --extension js --extension ts | head -20

Length of output: 287


🏁 Script executed:

#!/bin/bash
# Get issue `#208` details
gh issue view 208 --repo kunalverma2512/CodeLens --json title,body,comments

Length of output: 31897


🏁 Script executed:

#!/bin/bash
# Find and read test files for authMiddleware
fd -t f "authMiddleware" | head -10
fd -t f -g "*auth*test*" -o -g "*test*auth*" | head -10
fd -t f "test" --extension js --extension ts | xargs grep -l "authMiddleware\|silent.refresh\|refreshToken" 2>/dev/null | head -10

Length of output: 479


🏁 Script executed:

#!/bin/bash
# Read user model to understand security.refreshTokenHash structure
fd -t f "User" --extension js --extension ts | head -5
fd -t f "user" --extension js --extension ts | xargs grep -l "refreshTokenHash\|security" 2>/dev/null | head -5

Length of output: 335


🏁 Script executed:

#!/bin/bash
# Check the auth refresh route
fd -t f --extension js --extension ts | xargs grep -l "refreshTokenHash" 2>/dev/null | head -10

Length of output: 276


🏁 Script executed:

#!/bin/bash
cat server/modules/auth/controller.js

Length of output: 10689


🏁 Script executed:

#!/bin/bash
cat server/utils/tokenHelper.js
cat server/models/User.js

Length of output: 5518



@kunalverma2512 Here is my full, production-readiness verdict on PR #211 by @riya-chauhan12.


✅ What's Correct (the security core)

The fundamental fix is directionally right. The contributor correctly identified and patched the root cause: the silent refresh path now performs the same four-step validation as POST /api/auth/refresh:

Step PR #211 controller.js
1. JWT signature check
2. DB user fetch with +security.refreshTokenHash
3. Existence check (!storedHash)
4. bcrypt.compare() against stored hash

This is the right approach and addresses the issue at its root.


❌ Issues That Must Be Fixed Before Merging

1. 🔴 Missing clearAuthCookies on revoked session (session management broken)

The controller.js reference implementation does this when !storedHash:

// controller.js — token theft path
await User.findByIdAndUpdate(userId, { $unset: { "security.refreshTokenHash": "" } });
clearAuthCookies(res); // ← clears stale cookies on the client
throw new ApiError(401, "Invalid session...");

The PR's middleware does neither when !storedHash or when !isValid. This means the stale/revoked cookie stays alive on the client, the browser will hammer the same failed request on every page load, and the session is never fully cleaned up. clearAuthCookies is already exported from tokenHelper.js — it just needs to be imported and used.

The fix:

// At the top of authMiddleware.js — add clearAuthCookies to the import
import { verifyToken, verifyRefreshToken, generateAccessToken, setAccessTokenCookie, clearAuthCookies } from "../utils/tokenHelper.js";

// When !storedHash:
clearAuthCookies(res);
throw new ApiError(401, "Session has been revoked. Please log in again.");

// When !isValid:
await User.findByIdAndUpdate(userId, { $unset: { "security.refreshTokenHash": "" } });
clearAuthCookies(res);
throw new ApiError(401, "Invalid session. Please log in again.");

2. 🔴 No test files committed to the repository

The PR description describes two local regression tests, but zero test files exist in the repo. This is a security-critical auth fix deployed on Render — the tests must be committed so regressions can be caught in CI, not discovered in production.


3. 🟡 Double DB query on the silent refresh path (performance regression)

The original code did 1 DB query (the User.findById at the bottom of the middleware). The PR now does 2 on every silent refresh:

// Query 1 — inside silent refresh branch (new)
const refreshUser = await User.findById(userId).select("+security.refreshTokenHash");

// Query 2 — always runs afterward (original, unchanged)
const user = await User.findById(userId).select("-password");

Since refreshUser is already available after validation, req.user can be set directly from it, skipping the second query entirely. The only difference is refreshUser will include the refreshTokenHash field, but that's fine — select: false means it won't appear in JSON responses automatically, and req.user is an in-memory object anyway.


4. 🟡 Inconsistent indentation and formatting throughout the PR

This is a production project and the new code has multiple formatting issues:

// ❌ Unaligned 401 argument (3-space indent)
throw new ApiError(
   401,
"Session has been revoked..."
);

// ❌ Extra indentation on closing paren
const isValid = await bcrypt.compare(
  req.cookies.refreshToken,
  storedHash
  );           // ← should align with the opening paren's block

// ❌ Space before comma, double space mid-object
decoded = {userId: refreshUser._id ,email: refreshUser.email,  role: refreshUser.role

// ❌ Broken brace placement
      } 
      }else {  // ← should be `} else {` on its own line

This is a minor but real concern for a shared codebase — inconsistency makes future diffs harder to read and signals incomplete attention to detail.


Summary: Is This PR Ready to Merge?

Not yet. The security core is correct and the contributor understood the problem well. However:

Issue Severity Required?
Missing clearAuthCookies (revoked + invalid paths) 🔴 Critical Yes
No test files committed 🔴 Critical Yes
Double DB query on silent refresh 🟡 Should fix Strongly recommended
Indentation/formatting 🟡 Should fix Yes for a production codebase

@riya-chauhan12 please address the above four points before this PR is merged. The core logic is solid — these are the remaining gaps to bring it to production quality. Specifically, the clearAuthCookies + optional DB $unset calls on the rejection paths are the most important change, as they mirror the contract already established in controller.js.

🐇 (\\_/) 🔍
( •.•) — almost there, a few more hops needed

@kunalverma2512

Copy link
Copy Markdown
Owner

Hey @riya-chauhan12,

Great work identifying and tackling this correctly — the core logic of your fix is right and your understanding of the problem is solid. You've successfully added the DB hash validation that was missing.

Before I merge this, there are 3 specific things you need to address. Please read all of them carefully and understand why each one matters before touching any code.


🔴 Issue 1 — Missing clearAuthCookies on Rejection Paths (Most Important)

What the problem is

When your fix correctly rejects a revoked session (!storedHash) or an invalid token (!isValid), it throws a 401 — but it leaves the stale cookies sitting in the user's browser unchanged.

This means the browser will keep sending those dead cookies on every future request, creating a loop of failed requests. The user will be stuck in a broken state.

What to study

Look at how controller.js handles the same rejection on lines 163–166.

It does two things on !isValid:

  • $unset the hash from DB
  • clearAuthCookies(res) before throwing

Your middleware needs to do the same.

What you need to do

Import clearAuthCookies from tokenHelper.js (it's already exported, just not imported in authMiddleware.js currently).

Then call it before throwing on both rejection paths:

  • when !storedHash
  • when !isValid

🟡 Issue 2 — Double DB Query on Every Silent Refresh (Performance)

What the problem is

Your fix adds a User.findById() inside the silent refresh block to get the hash.

But the original middleware already does another User.findById() on line 70 right after the try-catch block (to populate req.user).

So every silent refresh now hits MongoDB twice for the same user document.

What to study

Look at authMiddleware.js line 70:

const user = await User.findById(userId).select("-password");

This runs unconditionally after the try-catch.

The user object you fetched inside the silent refresh block is the same user — just fetched again.

What you need to do

After your validation passes inside the silent refresh block, store the fetched user object in a variable scoped outside the inner try-catch.

Then at line 70, check if you already have the user object from the silent refresh.

If yes:

  • skip the second DB call
  • use the one you already fetched

This eliminates one redundant MongoDB round-trip on every silent refresh.


🟡 Issue 3 — Code Formatting Inconsistencies

What the problem is

The PR has several formatting issues:

  • Misaligned closing parentheses
  • Inconsistent spacing around commas
  • Incorrect brace placement (}else{ instead of } else {)

This is a production codebase so formatting needs to be clean and consistent.

What to do

Before opening your next commit, go through the changed lines carefully and match the existing code style in authMiddleware.js.

Pay special attention to:

  • Closing parentheses alignment
  • Spacing around operators and commas
  • } else { formatting

📋 Summary of What to Do Next

First

Read and understand WHY each issue above is a problem.

Don't just patch blindly.

Then

Look at:

  • controller.js lines 149–167
  • tokenHelper.js

as your reference.

Your fix should feel like a natural extension of what already exists there.

Then

Make the changes in a new commit on the same branch.

It will automatically update this PR.

Finally

Come back here and tag me with:

  • What you changed
  • Why you changed it
  • How the updated implementation addresses each issue

The security core of your fix is correct.

These remaining items are what separate a good fix from a production-ready one.

Looking forward to seeing the updated version! 💪

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/middlewares/authMiddleware.js`:
- Line 53: The sensitive `security.refreshTokenHash` field is being fetched and
cached as `prefetchedUser` which could expose this data in logs or serialization
downstream. After fetching the user document with the refreshTokenHash selector
at the locations where
`User.findById(userId).select("+security.refreshTokenHash")` is called, convert
the user document to a plain object using `.toObject()` and delete the
refreshTokenHash property before assigning it to `prefetchedUser`. This ensures
the sensitive hash is not cached on `req.user`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f1b3def-0754-402f-8ab3-610b8bba1e89

📥 Commits

Reviewing files that changed from the base of the PR and between 5729967 and 4ecdad5.

📒 Files selected for processing (1)
  • server/middlewares/authMiddleware.js

Comment thread server/middlewares/authMiddleware.js
@kunalverma2512

Copy link
Copy Markdown
Owner

@coderabbitai please check recent 3 commits and final one and tell whether final commit is well and good and ready to merge
Make sure you check the codes completely and no bad impact after merging must come in production

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get recent commits on the PR
gh pr view 211 --repo kunalverma2512/CodeLens --json commits --jq '.commits[-5:]'

Length of output: 1428


🏁 Script executed:

#!/bin/bash
# Get the current diff of the PR
gh pr diff 211 --repo kunalverma2512/CodeLens

Length of output: 5335


🏁 Script executed:

#!/bin/bash
# Read the current authMiddleware.js
cat server/middlewares/authMiddleware.js

Length of output: 4559


🏁 Script executed:

#!/bin/bash
# Check for test files
fd -t f -e js -e ts | xargs grep -l "authMiddleware\|silent.refresh\|refreshToken" 2>/dev/null | head -20
fd -t f -e test.js -e spec.js -e test.ts -e spec.ts | head -20
find . -name "*.test.*" -o -name "*.spec.*" | grep -i "auth" | head -20

Length of output: 662


🏁 Script executed:

#!/bin/bash
# Read tokenHelper.js to verify clearAuthCookies
cat server/utils/tokenHelper.js

Length of output: 2956


🏁 Script executed:

#!/bin/bash
# Read controller.js for comparison
cat server/modules/auth/controller.js

Length of output: 10725



@kunalverma2512 Here is my full production-readiness check of the 3 recent commits (last one being c9dd2c1).


✅ Issues 1 and 3 — Fully Fixed

clearAuthCookies on rejection paths (was 🔴 Critical):

Path clearAuthCookies called $unset DB hash
!storedHash (revoked session) ❌ (intentional — hash already gone)
!isValid (hash mismatch)

This now matches the controller.js reference implementation exactly.

Double DB query eliminated (was 🟡):
prefetchedUser is correctly scoped outside the inner try-catch and reused via ?? at the final User.findById line. Silent refresh now costs 1 DB query, not 2. ✅


❌ Issue 2 — Tests Still Not Committed

The repo has zero test files of any kind. This is a project-wide gap, not just this PR. The PR description mentions local regression tests but they were never committed.

For a security-critical fix on a production deployment (Render), this is still a risk — there's nothing in CI to catch regressions. This is the single most important thing still missing.


🟡 Issue 4 — Formatting Partially Improved but Still Broken

The code logic is entirely correct, but the indentation structure of the outer try-catch block was shifted 2 spaces in the wrong direction in the latest commits. Specifically:

// ❌ These lines are at 6-space indent — should be 4:
      const userId = decoded.userId || decoded.id || decoded._id;
      if (!userId) { ... }
      const user = prefetchedUser ?? ...
      req.user = user;
      next();

// ❌ Outer catch is at 4-space indent — should be 2:
    } catch (error) {

// ❌ Closing `});` is at 5 spaces — should be 6:
     });

// ❌ Arrow function closing `};` is at 2 spaces — should be 0:
  };

Also a trailing space on the let prefetchedUser = null; line, and an extra blank line after the arrow function opening. These are cosmetic but real in a production shared codebase.


Final Verdict

Issue Status
Missing clearAuthCookies on both rejection paths ✅ Fixed
Double DB query on silent refresh ✅ Fixed
Formatting/indentation of outer try-catch block 🟡 Still broken (cosmetic, doesn't affect runtime)
Test files committed to repo ❌ Not done (project-wide gap)

The security fix is now complete and correct. The clearAuthCookies + $unset + prefetchedUser pattern mirrors controller.js exactly and closes the revocation bypass described in #208.

Before merging, I'd recommend:

  1. @riya-chauhan12 fix the indentation on the const userId block and the outer catch — the code got shifted 2 extra spaces during the last commit. Aligning it back to 4-space indent for that block and 2-space for the catch closes this out.
  2. @kunalverma2512 decide on the test files question — since the codebase has no tests at all, you may want to treat that as a separate follow-up issue rather than a hard blocker for this PR.

The core security work is done well. It's one formatting cleanup away from being merge-ready. 🐇

@kunalverma2512

Copy link
Copy Markdown
Owner

@riya-chauhan12 after your commits , ping me first and make sure you never apply changes or modification made my coderabbitai when it runs automatically after your commit because it might not review nicely

After your commit , ping me then I will ping coderabbitai and then study coderabbitai suggestions

@kunalverma2512

kunalverma2512 commented Jun 18, 2026

Copy link
Copy Markdown
Owner

@riya-chauhan12 dont work on test files now and leave that and please see no unneccessary spaces, tab space etc exist in files

Use prettier extension for formatting the file so that it follow uniformity

@riya-chauhan12

Copy link
Copy Markdown
Author

Hi @kunalverma2512,

I've pushed the requested changes and addressed the review points:

  • Added clearAuthCookies(res) on the rejection paths.
  • Mirrored the invalid-session cleanup behavior from controller.js.
  • Removed the redundant database query by reusing the prefetched user during silent refresh.
  • Cleaned up the formatting issues in the modified section.

I also addressed the latest CodeRabbit feedback regarding security.refreshTokenHash being carried through the prefetched user.

I've verified the updated behavior locally and will wait for your review before making any further changes.

Please let me know if there is anything else you would like adjusted. Thanks again for the review and guidance.

@kunalverma2512

Copy link
Copy Markdown
Owner

Hi @kunalverma2512,

I've pushed the requested changes and addressed the review points:

  • Added clearAuthCookies(res) on the rejection paths.
  • Mirrored the invalid-session cleanup behavior from controller.js.
  • Removed the redundant database query by reusing the prefetched user during silent refresh.
  • Cleaned up the formatting issues in the modified section.

I also addressed the latest CodeRabbit feedback regarding security.refreshTokenHash being carried through the prefetched user.

I've verified the updated behavior locally and will wait for your review before making any further changes.

Please let me know if there is anything else you would like adjusted. Thanks again for the review and guidance.

@riya-chauhan12 please let me know that have you understood what I said in previous comments ?

@kunalverma2512

Copy link
Copy Markdown
Owner

@coderabbitai please check @riya-chauhan12 latest commit regarding security.refreshTokenHash as when I asked before than you didnt pointed me for this so is this commit is well and good and improves backend or its not needed ?

please check coding done by @riya-chauhan12 and tell finally whats remaining ?
leave the test files as I am not currently need it and also dont point out for any unneccesary spacinng done by @riya-chauhan12 as I will see it afterwards
Check fully and come to what more to do or not and is ready to merge or not ?

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get recent commits on the PR
gh pr view 211 --repo kunalverma2512/CodeLens --json commits --jq '.commits[-5:]'

Length of output: 1428


🏁 Script executed:

#!/bin/bash
# Get current diff
gh pr diff 211 --repo kunalverma2512/CodeLens

Length of output: 5321


🏁 Script executed:

#!/bin/bash
# Read current authMiddleware.js
cat server/middlewares/authMiddleware.js

Length of output: 4555


🏁 Script executed:

#!/bin/bash
# Read tokenHelper.js
cat server/utils/tokenHelper.js

Length of output: 2929


🏁 Script executed:

#!/bin/bash
# Read controller.js for reference comparison
cat server/modules/auth/controller.js

Length of output: 10735


🏁 Script executed:

#!/bin/bash
cat server/models/User.js

Length of output: 2667



@kunalverma2512 Here is the complete assessment of @riya-chauhan12's latest commit and the overall final state of the PR.


About the Latest Commit: "fix: remove refresh token hash from prefetched user"

Is it needed? Yes — it's a valid and good defensive security measure.

Here's why: the silent refresh query uses .select("+security.refreshTokenHash") to explicitly load a field that is normally hidden (select: false in the schema). This means the in-memory refreshUser object has the bcrypt hash living on it. Without the cleanup, prefetchedUser (and therefore req.user) would carry that hash for the entire lifetime of the request.

Any downstream route handler that does something like res.json(req.user) or res.json({ user: req.user }) would accidentally expose the bcrypt hash in the API response. The fix correctly strips it before assignment:

if (refreshUser.security) {
  refreshUser.security.refreshTokenHash = undefined;
}
prefetchedUser = refreshUser;

This is the right approach and matches the same hygiene principle used elsewhere in the codebase (e.g., password is select: false in the schema specifically to prevent it from leaking).


Full Scorecard — Final State

Issue Status
clearAuthCookies on !storedHash path ✅ Fixed
clearAuthCookies + $unset on !isValid path ✅ Fixed
Double DB query eliminated via prefetchedUser ✅ Fixed
refreshTokenHash stripped from req.user on silent refresh ✅ Fixed (latest commit)
Password leaking through prefetchedUser ✅ Not an issue — password: select: false in schema
ApiError re-thrown correctly (no swallowing) ✅ Correct
Revocation logic mirrors controller.js exactly ✅ Correct
Test files ⏭️ Deferred per your instruction
Formatting/indentation ⏭️ Deferred per your instruction

Final Verdict

The security core is complete, correct, and production-safe. The four critical issues from the original review are all addressed. The latest commit is a net improvement — not unnecessary work. The PR closes the revocation bypass described in #208 across all authentication flows (explicit /api/auth/refresh endpoint and the silent refresh middleware path).

Ready to merge once you're satisfied with formatting. 🐇

@kunalverma2512

Copy link
Copy Markdown
Owner

@riya-chauhan12 Outstanding work! 🎉

You didn't just fix the surface bug you thought through the full chain of implications. You identified the root cause, implemented the correct four-step validation, handled cookie cleanup on both rejection paths, eliminated the double DB query, and then on your own initiative caught and fixed the hash leakage risk on req.user. That last commit was never asked for you spotted it yourself, which is exactly the mindset that separates a good developer from a great one.

Merging this PR now. Well done!


🧠 What you can take away from this PR and where to grow next:

This PR touched one of the most nuanced areas in backend development the gap between cryptographic validity and application-level trust. You learned that a JWT being structurally valid is never enough on its own; the database is the ground truth for session state. From here, I'd encourage you to go deeper into stateless vs. stateful session management, study how refresh token rotation and family-based revocation work (where a single stolen token invalidates an entire device family), explore OWASP's session management guidelines, and look into how production systems like GitHub and Stripe handle token lifecycle at scale. Understanding select: false in Mongoose schema design, bcrypt cost factors, and HttpOnly cookie security are also concepts worth mastering deeply. You have a strong instinct for security-minded thinking keep sharpening it. 🚀

@kunalverma2512 kunalverma2512 merged commit 1145fc1 into kunalverma2512:main Jun 18, 2026
1 of 2 checks passed
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
codelensx Ready Ready Preview, Comment Jun 18, 2026 9:04am

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Silent Refresh Path Bypasses Refresh Token Revocation and Rotation Checks

2 participants