Robust CSRF Protection
FairArena Security Team
5 min read

Robust CSRF Protection

SecurityNext.jsCSRFBackend

How we secured our authentication flows against Cross-Site Request Forgery (CSRF) attacks without compromising user experience.

The Vulnerability

Cross-Site Request Forgery (CSRF) is a perennial threat for web applications. It allows an attacker to trick a user into executing unwanted actions on a web application where they are currently authenticated. At FairArena, as we scaled our enterprise features, we noticed gaps in our CSRF defenses, particularly within our intricate auth flows.

We initially relied on exempting numerous routes from CSRF checks to "get things working," a classic mistake that accumulates technical debt and security risk.

The Solution: A Dedicated Token Endpoint

Instead of a patchwork of exemptions, we implemented a dedicated endpoint to issue CSRF tokens securely.

Backend Implementation

We created a new route at /api/v1/auth/csrf. This endpoint does one thing: it generates a cryptographically secure token and sets it as a httpOnly cookie, while also returning it in the response body for the client to include in headers.

// Simplified CSRF Token Generation
router.get('/csrf', (req, res) => {
  const token = generateToken(req.session.id);
  res.cookie('X-CSRF-Token', token, { httpOnly: true, secure: true });
  res.json({ csrfToken: token });
});

Frontend Interceptor

On the client side, we updated our Axios instance to be "CSRF-aware". Before making any state-changing request (POST, PUT, DELETE), the client checks for the existence of a valid token. If missing, it proactively fetches a new one.

apiClient.interceptors.request.use(async (config) => {
  if (config.method !== 'get' && !hasValidCsrfToken()) {
    const { csrfToken } = await fetchCsrfToken();
    config.headers['X-CSRF-Token'] = csrfToken;
  }
  return config;
});

Security Architecture

Handling Edge Cases

The trickiest part was handling "cold" starts—like when a user clicks a verification email link. In these cases, the user has no session and no token. We implemented a "lazy" token generation strategy for public routes, ensuring that even unauthenticated users have a secure context for their initial interactions.

Conclusion

Security is not a feature you can bolt on; it's a process. By standardizing our CSRF flow, we removed dozens of dangerous route exemptions and created a safer environment for our users.