Skip to content

Feature: Add JWT Refresh Token Rotation and Secure Cookie Storage #71

@rishabh0510rishabh

Description

@rishabh0510rishabh

Problem Statement

The current authentication mechanism exposes the application to security vulnerabilities, specifically Cross-Site Scripting (XSS). If authentication tokens are stored in localStorage or sessionStorage on the client side, malicious scripts can easily access and exfiltrate them. Additionally, relying on a single long-lived JWT requires users to re-login frequently if the expiration is short, or exposes a larger security window if the expiration is long.

To mitigate this, we need to separate authentication into two tokens: a Short-lived Access Token for API requests and a Long-lived Refresh Token for session maintenance.

Current Behavior / Limitation

  • Single Token Flow: Users likely receive a single JWT upon login.
  • Insecure Storage: If the frontend stores this token in local storage, it is accessible via JavaScript.
  • Session UX: When the token expires, the user is immediately forced to log out, or the token is set with a very long expiration time, which is a security risk if the token is stolen.
  • Missing Endpoints: There is no dedicated endpoint to renew an access token without user credentials.

Expected Improvement

  • Access Tokens should be short-lived (e.g., 15 minutes) and sent to the client in the JSON response body (kept in memory by the frontend).
  • Refresh Tokens should be long-lived (e.g., 7 days) and sent in an HttpOnly, Secure, SameSite cookie. This makes the token inaccessible to client-side JavaScript, preventing XSS attacks from stealing the session.
  • A new /refresh-token endpoint will allow the client to silently acquire a new Access Token using the HttpOnly cookie.

Proposed Approach

1. Update Configuration

Ensure new environment variables are available (e.g., REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRY).

2. Database Schema (server/models/User.js)

  • (Optional but recommended) Add a refreshToken array field to the User schema if we want to support multi-device login or token invalidation/rotation on the server side.

3. Controller Logic (server/controllers/auth.controller.js)

  • Login/Register:
    • Generate Access Token (short expiration).
    • Generate Refresh Token (long expiration).
    • Send Access Token in the JSON response.
    • Send Refresh Token in an HTTP cookie with flags: httpOnly: true, secure: true (in production), sameSite: 'strict'.
  • New Endpoint (refresh-token):
    • Read the cookie from the request.
    • Verify the Refresh Token signature.
    • (Optional) Check if the token exists in the User DB record (Token Rotation).
    • Issue a new Access Token.
  • Logout:
    • Clear the Refresh Token cookie.

4. Middleware (server/middleware/auth.middleware.js)

  • Ensure the middleware only looks for the Access Token in the Authorization: Bearer <token> header.
  • It should reject expired tokens, prompting the frontend (via 401/403) to hit the refresh endpoint.

Verification Steps

These steps are crucial to ensure the security features are working as intended.

  1. Environment Setup: Ensure you have .env variables set for ACCESS_TOKEN_SECRET and REFRESH_TOKEN_SECRET.
  2. Test Login (Postman/cURL):
    • Send a POST request to /api/auth/login.
    • Check Body: Ensure it contains an accessToken.
    • Check Cookies: Ensure the response sets a cookie named refreshToken (or similar). Verify the flag HttpOnly is checked.
  3. Test Protected Route:
    • Copy the accessToken from the login response.
    • Send a GET request to a protected route adding the header Authorization: Bearer <your_access_token>.
    • Result: Should return 200 OK data.
  4. Test Refresh Logic:
    • Wait for the Access Token to expire (or temporarily set the expiry to 10s in code).
    • Send a GET request to the new /api/auth/refresh-token endpoint (ensure Postman is sending cookies).
    • Result: The API should return a new accessToken.
  5. Test Logout:
    • Hit the logout endpoint.
    • Check Cookies: The refreshToken cookie should be empty or expired.

label ECWoC26

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions