How the CSRF Protection Plugin Works with the freeCodeCamp API

The freeCodeCamp API prevents Cross-Site Request Forgery attacks by using a Fastify plugin located in api/src/plugins/csrf.ts that issues double-submit cookie tokens, validates them against a secret stored in an unsigned _csrf cookie, and requires clients to echo the token in a csrf-token header for state-changing requests.

The freeCodeCamp open-source codebase employs a dedicated CSRF protection plugin to secure its API endpoints against Cross-Site Request Forgery attacks. Located at api/src/plugins/csrf.ts, this Fastify plugin integrates the @fastify/csrf-protection library to implement a double-submit cookie pattern, ensuring that state-changing requests originate from legitimate authenticated sessions.

Plugin Registration and Architecture

The plugin lifecycle begins in api/src/app.ts, where the main application instance registers the CSRF plugin during server initialization. This registration occurs at lines 24–27, invoking await fastify.register(csrf) to inject the security logic into the request pipeline.

Inside api/src/plugins/csrf.ts, the plugin configures the external @fastify/csrf-protection module with two critical settings:

  • Token extraction: The getToken function reads the CSRF token from the csrf-token request header.
  • Secret storage: The plugin stores the CSRF secret in an unsigned cookie named _csrf (referenced internally as CSRF_SECRET_COOKIE).

This configuration establishes the foundation for the double-submit cookie pattern used throughout the freeCodeCamp API.

The plugin attaches an onRequest hook that executes for every route except /signout. For each incoming request, the hook invokes reply.generateCsrf() to create a fresh token and writes it to a cookie named csrf_token (defined by the constant CSRF_COOKIE).

Critically, the csrf_token cookie is not marked as httpOnly. This intentional design choice allows client-side JavaScript to read the token value, enabling single-page applications to extract the token and submit it in the required csrf-token header for subsequent state-changing requests.

The secret cookie _csrf remains separate and unsigned, serving as the server-side verification key against which the submitted token is validated.

Request Validation and 403 Enforcement

Protected routes within api/src/app.ts (lines 78–85) explicitly attach the fastify.csrfProtection hook via fastify.addHook('onRequest', fastify.csrfProtection). This hook performs strict validation on every request:

  1. Token presence: The request must include a csrf-token header.
  2. Cookie matching: The header value must match the token stored in the csrf_token cookie.
  3. Secret validation: The _csrf cookie must exist and contain a valid secret.

If any validation check fails, the plugin immediately returns a 403 Forbidden response, blocking the request before it reaches the route handler. This enforcement applies to all routes registered under the protected schema, effectively preventing CSRF attacks against authenticated endpoints.

When a user initiates a sign-out action via the /signout route, the CSRF protection plugin explicitly skips token generation for this specific endpoint. Instead, the route handler invokes reply.clearOurCookies(), a helper defined in api/src/plugins/cookies.ts (lines 71–75), to clear both the _csrf secret cookie and the csrf_token cookie.

This cleanup ensures that residual authentication tokens cannot be exploited after the session terminates, maintaining security hygiene during the logout process.

Testing CSRF Protection in the freeCodeCamp API

The repository includes comprehensive unit tests in api/src/plugins/csrf.test.ts that verify token creation, secret handling, and request validation logic. These tests simulate the double-submit pattern by extracting cookies from initial responses and replaying them in subsequent requests.

Additionally, api/vitest.utils.ts provides helper utilities to streamline CSRF token extraction during integration testing, ensuring that developers can reliably validate protected endpoints within the Vitest testing framework.

Client Implementation Examples

Browser-Based Token Retrieval

When interacting with the freeCodeCamp API from a browser, client-side code must extract the token from the csrf_token cookie and include it in the request headers:

// Extract the CSRF token from the browser's cookie store
function getCsrfToken() {
  const match = document.cookie.match(/(?:^|; )csrf_token=([^;]*)/);
  return match ? decodeURIComponent(match[1]) : null;
}

// Perform a state-changing request with proper CSRF protection
async function postData(url, body) {
  const token = getCsrfToken();
  const response = await fetch(url, {
    method: 'POST',
    credentials: 'include',          // Transmit cookies (_csrf, csrf_token)
    headers: {
      'Content-Type': 'application/json',
      'csrf-token': token           // Required by the CSRF plugin
    },
    body: JSON.stringify(body)
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

Server-Side Testing Pattern

For server-side integration tests using Vitest, the following pattern validates the CSRF protection flow:

import { describe, test, expect } from 'vitest';
import Fastify from 'fastify';
import csrf, { CSRF_COOKIE, CSRF_SECRET_COOKIE } from '../../src/plugins/csrf.js';
import cookies from '../../src/plugins/cookies.js';

async function buildServer() {
  const app = Fastify();
  await app.register(cookies);
  await app.register(csrf);
  app.addHook('onRequest', app.csrfProtection);
  app.get('/protected', (_, reply) => reply.send({ ok: true }));
  return app;
}

describe('CSRF protected route', () => {
  test('accepts request with correct token', async () => {
    const server = await buildServer();

    // Initial request establishes the token and secret cookies
    const init = await server.inject({ method: 'GET', url: '/' });
    const tokenCookie = init.cookies.find(c => c.name === CSRF_COOKIE)!;
    const secretCookie = init.cookies.find(c => c.name === CSRF_SECRET_COOKIE)!;

    // Subsequent request must include both the secret cookie and token header
    const res = await server.inject({
      method: 'GET',
      url: '/protected',
      cookies: { [_csrf: secretCookie.value] },
      headers: { 'csrf-token': tokenCookie.value }
    });

    expect(res.statusCode).toBe(200);
    expect(res.json()).toEqual({ ok: true });
  });
});

Summary

  • The CSRF protection plugin resides in api/src/plugins/csrf.ts and integrates @fastify/csrf-protection into the freeCodeCamp API.
  • It implements a double-submit cookie pattern, storing a secret in the _csrf cookie and issuing tokens via the readable csrf_token cookie.
  • Protected routes enforce validation through the fastify.csrfProtection hook, returning 403 Forbidden for missing or mismatched tokens.
  • The plugin excludes the /signout route from token generation to facilitate proper cookie cleanup via reply.clearOurCookies().
  • Comprehensive test coverage in api/src/plugins/csrf.test.ts ensures the integrity of the CSRF defense mechanism.

Frequently Asked Questions

Where is the CSRF protection plugin located in the freeCodeCamp repository?

The plugin is implemented in api/src/plugins/csrf.ts. This file defines the Fastify plugin that configures @fastify/csrf-protection, manages cookie constants like CSRF_COOKIE and CSRF_SECRET_COOKIE, and registers the onRequest hook for token distribution.

How does the CSRF plugin validate incoming requests?

The plugin validates requests using the fastify.csrfProtection hook attached to protected routes. It verifies that the csrf-token header matches the value stored in the csrf_token cookie and confirms that the _csrf secret cookie exists and is valid. Failure of either check results in an immediate 403 Forbidden response.

The csrf_token cookie intentionally lacks the httpOnly flag to allow client-side JavaScript to read its value. This design enables single-page applications to extract the token and submit it in the csrf-token header for state-changing requests, which is required by the double-submit cookie validation pattern implemented in api/src/plugins/csrf.ts.

How do I test CSRF-protected routes in the freeCodeCamp API?

Tests should follow the pattern established in api/src/plugins/csrf.test.ts. First, make an initial request to obtain the csrf_token and _csrf cookies. Then, include the _csrf cookie in the request cookies and the csrf_token value in the csrf-token header for subsequent requests to protected endpoints. The api/vitest.utils.ts file provides helpers to streamline this extraction process.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →