auth-service.tsmiddleware.ts
@Agent Can you review these auth files? The JWT refresh logic seems broken — tokens expire but the middleware doesn't catch it.
Looking into the auth files now. Let me read through the code and trace the token refresh flow.
3 tool calls
Completereadauth-service.ts45ms
Completereadmiddleware.ts38ms
Completebashcli:remote-servergrep -rn "refreshToken" src/120ms
Found the issue. In middleware.ts, the token validation runs before the refresh check. When a token expires, the middleware rejects the request immediately instead of attempting a refresh.
Here's the problematic sequence:
  1. Request arrives with an expired access token
  2. validateToken() throws TokenExpiredError
  3. The catch block returns 401 — never reaching refreshToken()
The fix is to catch TokenExpiredError specifically and attempt a refresh before rejecting:
middleware.ts — typescript
async function authMiddleware(req: Request) {
try {
const decoded = validateToken(req.headers.authorization);
req.user = decoded;
} catch (err) {
if (err instanceof TokenExpiredError) {
// Attempt silent refresh before rejecting
const refreshed = await refreshToken(req.cookies.refreshToken);
if (refreshed) {
req.user = refreshed.user;
req.newAccessToken = refreshed.accessToken;
return next(req);
}
}
return new Response('Unauthorized', { status: 401 });
}
return next(req);
}
Completeeditmiddleware.ts+8-285ms
Nice catch, that makes sense
Can you also add a test for the refresh path?
3 tool calls
Completereadmiddleware.test.ts32ms
Completeeditmiddleware.test.ts+24-0110ms
Completebashcli:remote-serveryarn test middleware3.2s
Added a test for the refresh flow. All 4 tests pass:
TestStatus
Valid token passes through✅
Expired token triggers refresh✅
Expired token with invalid refresh returns 401✅
Malformed token returns 401 immediately✅
middleware.test.ts — typescript
describe('authMiddleware', () => {
it('refreshes an expired token silently', async () => {
const expiredToken = createExpiredJWT(mockUser);
const validRefresh = createRefreshToken(mockUser);
​
const req = mockRequest({
authorization: `Bearer ${expiredToken}`,
cookies: { refreshToken: validRefresh },
});
​
const res = await authMiddleware(req);
​
expect(res.status).toBe(200);
expect(req.user.id).toBe(mockUser.id);
expect(req.newAccessToken).toBeDefined();
});
});
Changes saved to workspace
Looks solid. Before I open the PR, can you write up a short design doc explaining the token-refresh flow for the team?
I've drafted a design doc covering the problem, the fix, and the test matrix — pulling straight from the changes we just made.
Open the document below to review it. Want me to expand any section?
JWT Token Refresh: Design & RolloutDocument
This is great. Can you add a section on rollout and monitoring at the end?
On it — adding a Rollout & Monitoring section with a staged flag ramp and the alert thresholds. Updating the document now.
Runningeditcli:remote-serverdocs/token-refresh.md

JWT Token Refresh: Design & Rollout

Overview

Our API gateway authenticates every request with a short-lived JWT access token. Until now, an expired token meant an immediate 401 — even when the user still held a valid refresh token. This document describes the silent-refresh flow we just shipped and how we're rolling it out.

The Problem

Token validation ran before any refresh logic, so the middleware rejected expired tokens outright:
  1. A request arrives with an expired access token
  2. validateToken() throws TokenExpiredError
  3. The catch block returns 401 — refreshToken() is never reached
The result was users getting logged out whenever an access token lapsed mid-session.

The Fix

The middleware now catches TokenExpiredError specifically and attempts a silent refresh before rejecting. On success it reissues an access token and continues the request; on failure it falls back to 401.
  • Transparent — valid sessions never see an interruption
  • Safe — a missing or invalid refresh token still returns 401
  • Cheap — refresh only runs on the expiry path, not on every request

Testing

The refresh path is covered end to end:
ScenarioExpected
Valid token passes through200
Expired token, valid refresh200 + new access token
Expired token, invalid refresh401
Malformed token401

Rollout & Monitoring

  1. Ship behind the silent_refresh flag at 5% of traffic
  2. Watch the auth.refresh.success and auth.refresh.failure counters
  3. Alert if the failure rate exceeds 2% over any 5-minute window
  4. Ramp to 100% once metrics hold steady for 24 hours

JWT Token Refresh: Design & Rollout

Document · Updated just now

JWT Token Refresh: Design & Rollout

Overview

Our API gateway authenticates every request with a short-lived JWT access token. Until now, an expired token meant an immediate 401 — even when the user still held a valid refresh token. This document describes the silent-refresh flow we just shipped and how we're rolling it out.

The Problem

Token validation ran before any refresh logic, so the middleware rejected expired tokens outright:
  1. A request arrives with an expired access token
  2. validateToken() throws TokenExpiredError
  3. The catch block returns 401 — refreshToken() is never reached
The result was users getting logged out whenever an access token lapsed mid-session.

The Fix

The middleware now catches TokenExpiredError specifically and attempts a silent refresh before rejecting. On success it reissues an access token and continues the request; on failure it falls back to 401.
  • Transparent — valid sessions never see an interruption
  • Safe — a missing or invalid refresh token still returns 401
  • Cheap — refresh only runs on the expiry path, not on every request

Testing

The refresh path is covered end to end:
ScenarioExpected
Valid token passes through200
Expired token, valid refresh200 + new access token
Expired token, invalid refresh401
Malformed token401

Rollout & Monitoring

  1. Ship behind the silent_refresh flag at 5% of traffic
  2. Watch the auth.refresh.success and auth.refresh.failure counters
  3. Alert if the failure rate exceeds 2% over any 5-minute window
  4. Ramp to 100% once metrics hold steady for 24 hours