From cb2fba6e8b4a7c8ed236113cd40084e100df4021 Mon Sep 17 00:00:00 2001 From: Rokojori Date: Sat, 18 Jul 2026 08:37:26 +0200 Subject: [PATCH] auth: Bearer-before-cookie token extraction; JWT_CLOCK_TOLERANCE for clock skew MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extractToken now checks Authorization: Bearer before the accessToken cookie so injected headers (Electron, API clients) cannot be shadowed by stale cookies. JWT_CLOCK_TOLERANCE env var (seconds) is passed to jwt.verify as clockTolerance when non-zero — set in .env for local dev to absorb clock skew vs production. Co-Authored-By: Claude Sonnet 4.6 --- source/server/auth.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/server/auth.ts b/source/server/auth.ts index 39fd6d2..b578196 100644 --- a/source/server/auth.ts +++ b/source/server/auth.ts @@ -19,15 +19,16 @@ const JWT_SECRET = process.env.JWT_SECRET ?? ''; const AUTH_HOST = process.env.AUTH_HOST ?? 'https://account.rokojori.com'; const AUTH_INTERNAL_HOST = process.env.AUTH_INTERNAL_HOST ?? AUTH_HOST; const COOKIE_DOMAIN = process.env.COOKIE_DOMAIN ?? '.rokojori.com'; +const CLOCK_TOLERANCE = parseInt( process.env.JWT_CLOCK_TOLERANCE ?? '0', 10 ); // ── Internal helpers ─────────────────────────────────────────────────────────── function extractToken( req: Request ): string | undefined { - const cookie = req.cookies?.accessToken as string | undefined; - if ( cookie ) return cookie; const header = req.headers.authorization; if ( header?.startsWith( 'Bearer ' ) ) return header.slice( 7 ); + const cookie = req.cookies?.accessToken as string | undefined; + if ( cookie ) return cookie; return undefined; } @@ -100,7 +101,7 @@ export function jwtMiddleware( req: Request, res: Response, next: NextFunction ) try { - req.auth = jwt.verify( token, JWT_SECRET ) as AuthPayload; + req.auth = jwt.verify( token, JWT_SECRET, CLOCK_TOLERANCE ? { clockTolerance: CLOCK_TOLERANCE } : {} ) as AuthPayload; req.rawToken = token; next(); return;