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
getTokenfunction reads the CSRF token from thecsrf-tokenrequest header. - Secret storage: The plugin stores the CSRF secret in an unsigned cookie named
_csrf(referenced internally asCSRF_SECRET_COOKIE).
This configuration establishes the foundation for the double-submit cookie pattern used throughout the freeCodeCamp API.
Automatic Token Generation and Cookie Distribution
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:
- Token presence: The request must include a
csrf-tokenheader. - Cookie matching: The header value must match the token stored in the
csrf_tokencookie. - Secret validation: The
_csrfcookie 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.
Secure Sign-Out and Cookie Cleanup
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.tsand integrates@fastify/csrf-protectioninto the freeCodeCamp API. - It implements a double-submit cookie pattern, storing a secret in the
_csrfcookie and issuing tokens via the readablecsrf_tokencookie. - Protected routes enforce validation through the
fastify.csrfProtectionhook, returning 403 Forbidden for missing or mismatched tokens. - The plugin excludes the
/signoutroute from token generation to facilitate proper cookie cleanup viareply.clearOurCookies(). - Comprehensive test coverage in
api/src/plugins/csrf.test.tsensures 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.
Why is the csrf_token cookie not marked as httpOnly?
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →