csrf-csrf

repository·main·Indexed 41 days ago

https://github.com/psifi-solutions/csrf-csrf

A utility package for implementing stateless CSRF protection in Express applications using the signed Double Submit Cookie Pattern. Designed as a modern, configurable alternative to the deprecated csurf package, it is optimized for SPAs and cross-site frontends. Version 4.0.3.

Tokens
7.7K
Snippets
19
Records
31
Agent score
52%

What's inside csrf-csrf

  1. Choose between `csrf-csrf` and `csrf-sync`

    main

    The choice depends on your authentication state management:

    • Use csrf-sync: If you are using session-based authentication with server-side state (e.g., using express-session).
    • Use csrf-csrf: If you are using stateless authentication, such as a JWT stored in an httpOnly cookie.
  2. Quickstart: Implement stateless CSRF protection

    main

    Initialize doubleCsrf with the required getSecret and getSessionIdentifier functions. This provides utilities to protect routes and generate tokens.

    Important Middleware Order:

    1. express-session (if using sessions)
    2. cookie-parser
    3. doubleCsrf utilities/middleware

    Note: If using express-session, register cookie-parser after the session middleware to avoid conflicts.

    import { doubleCsrf } from "csrf-csrf";
    
    const { 
      doubleCsrfProtection, 
      generateCsrfToken 
    } = doubleCsrf({
      getSecret: (req) => 'return some cryptographically pseudorandom secret here',
      getSessionIdentifier: (req) => req.session.id
    });
    
    // Use doubleCsrfProtection as middleware to protect routes
    app.use(doubleCsrfProtection);
    
    // Use generateCsrfToken in a route to provide a token to the client
    app.get('/token', (req, res) => {
      const token = generateCsrfToken(req, res);
      res.json({ csrfToken: token });
    });
  3. Install new dependencies in running Docker containers

    main

    If you are running the example via Docker and need to add a new npm dependency, you must run npm install inside the container to ensure the environment is updated. Use docker exec to access the container shells:

    docker exec -it csrf-backend sh
    docker exec -it csrf-client sh

    Once inside, navigate to the /app directory and run npm install.

    docker exec -it csrf-backend sh
    docker exec -it csrf-client sh
  4. Run the React example with Docker

    main

    To run the full stack (React frontend, Express backend, and Redis) using Docker, follow these steps:

    1. Prepare the backend: Navigate to the backend directory and build the project.
    2. Start the containers: From the example/react directory, use Docker Compose to start the services in detached mode.
    3. Access the app: The React application will be available at http://localhost:3700/.

    Port Mapping:

    • Frontend (React): 3700
    • Backend (Express): 3710
    • Redis: 3779 (maps to 6379 in container)
    • Remote Debugging: 9229

    If these ports are occupied, update them in docker-compose.yml before starting.

    # In backend directory
    npm install
    npm run build
    
    # In example/react directory
    docker compose up -d
    
    # To stop
    docker compose down
  5. Install csrf-csrf and cookie-parser

    main

    To use csrf-csrf, you must install it along with cookie-parser. Ensure cookie-parser is registered as middleware in your Express application before the doubleCsrfProtection middleware.

    npm install cookie-parser csrf-csrf
  6. Decide whether to protect unauthorized routes (e.g. login)

    main

    Whether you need to protect routes like /login or /forgot-password depends on your session model:

    • Session-based (with anonymous sessions): If you generate a session for all users (even those not logged in), you must protect these routes.
    • Session-based (without anonymous sessions): If sessions only exist for logged-in users, you can skip protecting these routes.
    • JWT-based: Since the "session" does not exist until after login, these routes typically do not need CSRF protection.
    • OAuth2: Ensure the state parameter is used appropriately. For SPAs with no backend, use the PKCE flow. For SPAs with a backend, prioritize the Authorization Code Grant flow.
  7. How to generate and use CSRF tokens

    main

    To protect a route, the client must include the CSRF token in an appropriate request header (default is x-csrf-token) or the request body.

    You can generate a token using generateCsrfToken(req, res) or by calling req.csrfToken() on the request object after the doubleCsrfProtection middleware has executed.

    Token Reuse vs Overwrite:

    • By default, generateCsrfToken reuses an existing valid token from the cookie.
    • Use { overwrite: true } to force the generation of a new token and a new cookie.
    // Option 1: Using the utility function
    const csrfToken = generateCsrfToken(req, res, { overwrite: true });
    
    // Option 2: Using the request property (after middleware has run)
    const csrfToken = req.csrfToken({ overwrite: true });
  8. Security best practices for secrets

    main

    When configuring the secret for csrf-csrf, follow these guidelines:

    • Management: Refer to OWASP Secrets Management for creation, storage, and processing.
    • Environment Variables: Use actual user-scoped environment variables on the host system. Avoid using dotenv in production; use dotenvx if necessary, or better yet, use a dedicated secrets manager/vault.
    • Principle of Least Privilege: Run your application under a dedicated user with the minimum permissions required.
  9. Upgrade from Version 3 to 4

    main

    Upgrading to version 4 introduces breaking changes in token generation, configuration, and utility functions. Most importantly, CSRF tokens are now generated using createHmac instead of createHash.

    Critical Note: Zero Downtime Upgrade (ZDU) Tokens generated with version 3 are not compatible with version 4. To avoid breaking user sessions during the upgrade, you must consider clearing existing CSRF tokens or forcing new CSRF token generation.

  10. Run the React example without Docker

    main

    If you prefer to run the services locally without Docker, follow these steps:

    1. Install dependencies: Run npm install in both the backend and client directories.
    2. Configure Backend: Create a .env file in the backend directory with the following required variables:
      • EXAMPLE_CSRF_SECRET: A secret string for CSRF protection.
      • EXAMPLE_ALLOWED_ORIGINS: The URL of your frontend (e.g., http://localhost:3700).
      • EXAMPLE_API_PORT: The port for the Express API (e.g., 3710).
      • EXAMPLE_SESSION_SECRET: A secret string for session management.
      • EXAMPLE_REDIS_HOST: The host for your Redis instance.
      • EXAMPLE_REDIS_PORT: The port for your Redis instance.
      • NODE_ENV: Set to development.
    3. Configure Client: Create a .env file in the client directory:
      • VITE_EXAMPLE_BASE_API_URL: The URL of your backend API (e.g., http://localhost:3710).
    4. Redis: Ensure a Redis instance is running and accessible via the configured host/port.
    5. Start Services: Run npm run dev in the backend directory and npm run dev in the client directory in separate terminals.
    # Backend .env
    EXAMPLE_CSRF_SECRET=Fake CSRF secret
    EXAMPLE_ALLOWED_ORIGINS="http://localhost:3700"
    EXAMPLE_API_PORT=3710
    EXAMPLE_SESSION_SECRET=Fake session secret
    EXAMPLE_REDIS_HOST=localhost
    EXAMPLE_REDIS_PORT=3779
    NODE_ENV=development
    
    # Client .env
    VITE_EXAMPLE_BASE_API_URL=http://localhost:3710
  11. Determine if you need CSRF protection

    main

    If you use cookies for authentication or authorization, you likely need CSRF protection. This is especially true if you use httpOnly cookies for JWTs or other stateless authentication.

    You can skip CSRF protection only if all of the following are true:

    1. You only support modern/evergreen browsers.
    2. You do not use traditional form submits (which do not trigger CORS preflight checks and can be submitted cross-origin by default).
    3. Your auth-related cookies have the sameSite attribute set to strict or lax.
    4. You have a tight and explicit CORS configuration where allowed origins are explicitly defined and the backend only accepts cookies from those specific domains.