Skip to content

CSRF Update#24

Merged
benazeem merged 1 commit into
mainfrom
API-LogicBuilding
Apr 18, 2026
Merged

CSRF Update#24
benazeem merged 1 commit into
mainfrom
API-LogicBuilding

Conversation

@benazeem

@benazeem benazeem commented Apr 18, 2026

Copy link
Copy Markdown
Owner

Updated CSRF logic

Summary by CodeRabbit

  • New Features

    • Added support for PATCH HTTP requests
  • Security Enhancements

    • Enhanced CSRF protection with improved token validation and session verification
    • Implemented mandatory origin header validation for unsafe operations
    • CSRF tokens now provided in login, registration, and token refresh responses
    • Updated security cookie handling for improved protection

… DB checks for csrf only JWT stateless checking
Copilot AI review requested due to automatic review settings April 18, 2026 02:21
@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4070a02e-c0c3-44d2-bdbb-dffcdba01869

📥 Commits

Reviewing files that changed from the base of the PR and between 247fdc3 and e5cb137.

📒 Files selected for processing (9)
  • src/config/corsConfig.ts
  • src/middleware/security/requireCsrf.ts
  • src/modules/auth/auth.controller.ts
  • src/modules/auth/auth.service.ts
  • src/modules/auth/utils/createSessionPayload.ts
  • src/modules/user/user.controller.ts
  • src/types/Auth.ts
  • src/utils/generateAllCookieTokens.ts
  • src/utils/tokens.ts

📝 Walkthrough

Walkthrough

This pull request refactors CSRF token handling across the authentication flow. CSRF tokens transition from simple hex strings to JWT-based tokens bound to sessions and users. The CSRF middleware adds strict origin/referer validation for unsafe HTTP methods. CORS configuration adds PATCH method support, and token generation logic is redistributed across auth and session modules.

Changes

Cohort / File(s) Summary
CORS Configuration
src/config/corsConfig.ts
Added PATCH to the allowed HTTP methods list in CORS configuration.
CSRF Validation Middleware
src/middleware/security/requireCsrf.ts
Replaced simple string comparison with JWT-based CSRF token verification via jwt.verify(). Added strict origin/referer header validation for unsafe methods. Implemented session and user binding checks against decoded JWT claims. Removed debug logging and helper functions.
Token Generation Utilities
src/utils/tokens.ts, src/utils/generateAllCookieTokens.ts, src/modules/auth/utils/createSessionPayload.ts
Changed generateCsrfToken() to accept userId and sessionId, generating JWT tokens instead of hex strings. Updated generateRefreshToken() to remove unused reduceTime parameter. Moved CSRF token generation responsibility from generateAllCookieTokens to createSessionPayload. Added CSRF token configuration constants from environment variables.
Authentication Module
src/modules/auth/auth.controller.ts, src/modules/auth/auth.service.ts
Updated cookie handling: renamed csrf-token to csrf_token, set httpOnly: false, and use REFRESH_AGE for expiry. Auth controllers now include csrfToken in JSON responses. refreshTokenController requires and validates CSRF cookie presence. Removed CSRF token generation from refreshTokens() service method.
Type Definitions
src/types/Auth.ts
Removed csrfToken field from RefreshTokenOutput type. SignOutput remains unchanged with CSRF token as required field.
User Module
src/modules/user/user.controller.ts
Added UnauthorizedError import. Changed getProfileController to return manually constructed response instead of using sendSuccess helper.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Middleware as CSRF Middleware<br/>(requireCsrf)
    participant JWT as JWT Verifier
    participant SessionStore as Session/User Store
    participant Handler as Route Handler

    Client->>Middleware: Unsafe Request<br/>(PUT/POST/DELETE/PATCH)<br/>with csrf-token header<br/>and csrf_token cookie
    
    Middleware->>Middleware: Validate origin/<br/>referer header present
    
    alt Missing origin/referer
        Middleware->>Client: 401 UnauthorizedError
    end
    
    Middleware->>Middleware: Validate origin<br/>format & allowlist
    
    alt Invalid/disallowed origin
        Middleware->>Client: 401 UnauthorizedError
    end
    
    Middleware->>Middleware: Extract CSRF token<br/>from header & cookie
    
    Middleware->>JWT: jwt.verify(token,<br/>CSRF_TOKEN_SECRET)
    
    alt JWT verification fails
        JWT->>Middleware: Error
        Middleware->>Client: 401 UnauthorizedError
    end
    
    JWT->>Middleware: Decoded payload<br/>{userId, sessionId}
    
    Middleware->>SessionStore: Verify decoded.sessionId<br/>matches req.sessionId
    
    alt Session mismatch
        Middleware->>Client: 401 UnauthorizedError
    end
    
    Middleware->>SessionStore: If present in token,<br/>verify decoded.userId<br/>equals req.user?.id
    
    alt User binding mismatch
        Middleware->>Client: 401 UnauthorizedError
    end
    
    Middleware->>Handler: next() - Token valid
    Handler->>Client: Proceed with request
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A token's journey, JWT-bound and true,
Sessions and users now verified through,
No more hex whispers, just claims in the air—
Origins checked with meticulous care! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch API-LogicBuilding

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.

@benazeem benazeem merged commit 40be53d into main Apr 18, 2026
3 of 4 checks passed
@benazeem benazeem deleted the API-LogicBuilding branch April 18, 2026 02:22

Copilot AI 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.

Pull request overview

This PR updates the CSRF implementation by moving from random CSRF tokens to JWT-based CSRF tokens and adjusts related cookie/refresh-session behavior across the auth flow.

Changes:

  • Introduces JWT-signed CSRF tokens with dedicated secret/expiry env vars.
  • Renames the CSRF cookie to csrf_token and updates CSRF middleware validation accordingly.
  • Adjusts token refresh response/cookie setting and expands CORS methods to include PATCH.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/utils/tokens.ts Adds CSRF JWT env config and updates CSRF token generation.
src/utils/generateAllCookieTokens.ts Stops returning CSRF token from the “all cookie tokens” helper.
src/types/Auth.ts Removes csrfToken from RefreshTokenOutput.
src/modules/user/user.controller.ts Changes get-profile response formatting and imports.
src/modules/auth/utils/createSessionPayload.ts Generates CSRF JWT alongside access/refresh tokens.
src/modules/auth/auth.service.ts Removes CSRF token rotation from refresh-tokens service output.
src/modules/auth/auth.controller.ts Renames CSRF cookie, returns CSRF token in login/register/refresh responses, updates refresh-session handling.
src/middleware/security/requireCsrf.ts Switches CSRF validation to JWT verification + session/user binding.
src/config/corsConfig.ts Adds PATCH to allowed CORS methods.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 97 to +113
export const refreshTokenController = async (req: Request, res: Response) => {
const reqRefreshToken = req.cookies['refresh-token'];
const userAgent = req.headers['user-agent'] || 'unknown';
const ip = req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.ip || req.socket.remoteAddress;
const csrfToken = req.cookies?.['csrf_token'];

if (!reqRefreshToken) {
throw new UnauthenticatedError('No refresh token, authentication failed');
}

if (!csrfToken) {
throw new UnauthenticatedError('Missing CSRF token');
}

const tokens = await refreshTokens(reqRefreshToken, userAgent, ip);
setAuthCookies(res, tokens);
const allTokens = { ...tokens, csrfToken };
setAuthCookies(res, allTokens);

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

refreshTokenController only checks that the csrf_token cookie exists, but it does not require/validate an x-csrf-token header (double-submit) and does not verify the CSRF JWT against the refresh token’s session/user. A cross-site POST can still trigger a refresh because the browser will attach both cookies automatically. Require a header token (and compare to the cookie) and verify the CSRF token is bound to the refresh token’s decoded sessionId/userId (or otherwise apply CSRF middleware logic tailored for refresh).

Copilot uses AI. Check for mistakes.
Comment on lines 41 to +47
.cookie('access-token', tokens.accessToken, { ...cookieOptions, maxAge: ACCESS_AGE })
.cookie('refresh-token', tokens.refreshToken, { ...cookieOptions, maxAge: REFRESH_AGE })
.cookie('csrf-token', tokens.csrfToken, { ...cookieOptions, httpOnly: false, maxAge: CSRF_AGE });
.cookie('csrf_token', tokens.csrfToken, {
...cookieOptions,
httpOnly: false,
maxAge: REFRESH_AGE,
});

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

Cookie name changed from csrf-token to csrf_token. This will break existing logged-in sessions after deploy because clients will still have the old cookie, and csrfMiddleware now only reads csrf_token. Consider a migration period where you accept both cookie names (and/or set/clear both) so existing sessions can continue without forcing users to re-authenticate.

Copilot uses AI. Check for mistakes.
.clearCookie('access-token')
.clearCookie('refresh-token')
.clearCookie('csrf-token')
.clearCookie('csrf_token')

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

logoutController now clears only csrf_token. If a user still has the legacy csrf-token cookie, it will remain set after logout. Consider clearing both cookie names during the transition to avoid leaving stale CSRF cookies behind.

Suggested change
.clearCookie('csrf_token')
.clearCookie('csrf_token')
.clearCookie('csrf-token')

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +39
const headerTokenRaw = req.headers['x-csrf-token'];
const headerToken = Array.isArray(headerTokenRaw) ? headerTokenRaw[0] : headerTokenRaw;

const csrfCookieRaw = req.cookies['csrf-token'];
const cookieToken = req.cookies?.['csrf_token'];

if (!csrfHeader || !csrfCookieRaw) {
return next(new UnauthorizedError('CSRF credentials missing'));
if (!headerToken || !cookieToken) {
return next(new UnauthorizedError('Missing CSRF token'));
}

const csrfToken = normalizeToken(csrfHeader);
const csrfCookie = normalizeToken(csrfCookieRaw);
if (headerToken !== cookieToken) {
return next(new UnauthorizedError('CSRF token mismatch'));
}

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

The new CSRF check does a raw headerToken !== cookieToken comparison without normalization/decoding. In practice, cookie values can be quoted or URL-encoded by clients/proxies, which the previous normalizeToken handled; this change can cause valid requests to be rejected. Consider restoring normalization (decode + trim quotes) and using a timing-safe comparison to avoid regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +40
import { BadRequestError, UnauthorizedError } from '@middleware/error/index.js';

export const getProfileController = async (req: Request, res: Response) => {
const userId = req.user!.id;
const user = await getProfile(userId);
return sendSuccess(res, user, 'Profile retrieved successfully');

return res.status(200).json({
message: 'Profile retrieved successfully',
data: user,
});

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

UnauthorizedError is imported but not used, and getProfileController bypasses sendSuccess, which is used throughout this controller for consistent response shaping/sanitization. Remove the unused import and consider using sendSuccess(res, user, 'Profile retrieved successfully') for consistency.

Copilot uses AI. Check for mistakes.
Comment thread src/utils/tokens.ts
Comment on lines +13 to +15
const CSRF_TOKEN_EXPIRES_IN: SignOptions['expiresIn'] = process.env
.CSRF_TOKEN_EXPIRES_IN as SignOptions['expiresIn'];
const CSRF_TOKEN_SECRET: string = process.env.CSRF_TOKEN_SECRET!;

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

CSRF_TOKEN_EXPIRES_IN / CSRF_TOKEN_SECRET are read with non-null assertions. If these env vars are missing or misconfigured, CSRF token generation/verification will fail at runtime with a hard crash or auth errors. Consider validating required env vars at startup (or providing a safe default) so misconfiguration is detected early and reported clearly.

Copilot uses AI. Check for mistakes.
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.

2 participants