Security

CSRF

Cross-Site Request Forgery (CSRF) is a security vulnerability where attackers trick authenticated users into executing unwanted actions on web applications, exploiting the browser's automatic inclusion of credentials like cookies.

What is CSRF?

Cross-Site Request Forgery (CSRF), also known as XSRF, represents a critical web security vulnerability where attackers trick authenticated users into executing unwanted actions on web applications. CSRF attacks exploit the browser’s automatic inclusion of authentication credentials—cookies, HTTP authentication, IP addresses—with every request to a domain, regardless of which site initiated that request. When users remain logged into sensitive applications like banking sites, email providers, or administrative panels, malicious sites can trigger actions on their behalf without their knowledge or consent.

Unlike Cross-Site Scripting (XSS) which requires injecting malicious code into trusted sites, CSRF attacks leverage the victim’s existing authenticated session from external sites. Attackers craft requests that perform sensitive operations—transferring funds, changing passwords, modifying email addresses, posting content—and trick users into submitting these requests through deceptive links, hidden forms, or embedded images. The target application sees legitimate credentials attached to these requests and processes them as if the user intentionally initiated the action.

How CSRF Attacks Work

CSRF attacks exploit the fundamental way browsers handle authentication. When users log into web applications, servers typically issue session cookies that browsers automatically include with subsequent requests to that domain. This automatic credential inclusion enables seamless browsing experiences—users don’t re-authenticate for every action—but creates security risks when malicious sites forge cross-origin requests.

The attack sequence follows a predictable pattern. First, victims authenticate to a target application (banking site, social media platform, content management system) establishing a valid session. The authentication cookie remains active in the browser, automatically included with requests to that domain. Next, attackers craft malicious requests targeting sensitive operations on that application—requests that change settings, transfer resources, or modify data. These requests must match the target application’s expected format, including correct parameter names, HTTP methods, and URL structures.

// Example of a CSRF attack vector
// Malicious HTML on attacker.com
const csrfAttackExample = `
  <!-- Simple GET-based CSRF attack -->
  <img src="https://bank.com/transfer?to=attacker&amount=1000" style="display:none">

  <!-- POST-based CSRF attack using auto-submitting form -->
  <form id="csrf-form" action="https://bank.com/transfer" method="POST">
    <input type="hidden" name="to" value="attacker">
    <input type="hidden" name="amount" value="1000">
  </form>
  <script>
    document.getElementById('csrf-form').submit();
  </script>
`;

// The victim's browser automatically includes authentication cookies
// when making these requests to bank.com, making them appear legitimate

Attackers deliver these forged requests through various channels. Simple GET-based attacks embed malicious URLs in image tags, where browsers automatically fetch the URL attempting to load an image. More sophisticated POST-based attacks use hidden forms with JavaScript auto-submission, triggered when victims visit attacker-controlled pages. Email phishing campaigns, malicious advertisements, and compromised legitimate sites all serve as effective delivery mechanisms for CSRF attacks.

CSRF Protection Mechanisms

Modern web applications employ multiple defensive layers against CSRF attacks, with synchronizer tokens representing the most common and effective approach. CSRF tokens are unpredictable values generated server-side for each session or request, embedded in forms and verified when processing state-changing operations. Since attackers operating from external domains cannot read or predict these tokens due to Same-Origin Policy restrictions, they cannot craft valid CSRF attacks even if they know the application’s URL structure.

// Implementing CSRF token protection
class CSRFProtection {
  private tokens: Map<string, { token: string; expires: number }> = new Map();

  generateToken(sessionId: string): string {
    const token = crypto.randomUUID();
    const expires = Date.now() + 3600000; // 1 hour

    this.tokens.set(sessionId, { token, expires });
    return token;
  }

  validateToken(sessionId: string, providedToken: string): boolean {
    const stored = this.tokens.get(sessionId);

    if (!stored) {
      return false; // No token found for session
    }

    if (Date.now() > stored.expires) {
      this.tokens.delete(sessionId);
      return false; // Token expired
    }

    return stored.token === providedToken;
  }

  // Express middleware for CSRF protection
  middleware() {
    return (req: any, res: any, next: any) => {
      if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
        // Safe methods don't need CSRF protection
        return next();
      }

      const sessionId = req.session.id;
      const providedToken = req.headers['x-csrf-token'] || req.body._csrf;

      if (!this.validateToken(sessionId, providedToken)) {
        return res.status(403).json({
          error: 'Invalid CSRF token',
          message: 'Request rejected due to missing or invalid CSRF token'
        });
      }

      next();
    };
  }
}

// Using CSRF protection in Express application
import express from 'express';
const app = express();
const csrfProtection = new CSRFProtection();

app.use(csrfProtection.middleware());

// Endpoint to get CSRF token for client
app.get('/api/csrf-token', (req, res) => {
  const token = csrfProtection.generateToken(req.session.id);
  res.json({ csrfToken: token });
});

// Protected endpoint requiring CSRF token
app.post('/api/transfer', (req, res) => {
  // CSRF middleware validates token before reaching here
  const { to, amount } = req.body;
  // Process transfer...
  res.json({ success: true });
});

SameSite cookie attribute provides another powerful CSRF defense by instructing browsers not to send cookies with cross-origin requests. The SameSite=Strict setting completely prevents cookies from being sent with requests initiated by external sites, while SameSite=Lax allows cookies with top-level navigation (clicking links) but blocks them from embedded requests (forms, AJAX). Modern applications should set SameSite attributes on all authentication cookies as a foundational CSRF defense.

// Setting SameSite cookies for CSRF protection
const secureCookieOptions = {
  httpOnly: true,        // Prevents JavaScript access
  secure: true,          // Only sent over HTTPS
  sameSite: 'strict',    // Strongest CSRF protection
  maxAge: 3600000        // 1 hour expiration
};

// Express session configuration with SameSite
app.use(session({
  secret: 'your-secret-key',
  cookie: secureCookieOptions,
  name: 'sessionId'
}));

// Alternative: Lax mode for better usability
// Allows cookies when users click links from external sites
// but blocks them for forms and fetch requests
const laxCookieOptions = {
  ...secureCookieOptions,
  sameSite: 'lax'
};

Custom request headers provide additional CSRF defense for AJAX-based applications. Since browsers prevent JavaScript from setting certain custom headers on cross-origin requests without CORS permission, requiring custom headers like X-Requested-With: XMLHttpRequest ensures requests originate from trusted JavaScript code rather than simple forms or image tags. This approach works particularly well for single-page applications making all requests through fetch or XMLHttpRequest.

The double submit cookie pattern offers CSRF protection without requiring server-side state. Applications set a random value in a cookie and also require that same value in a request parameter or custom header. Since attackers cannot read cookies from cross-origin contexts due to Same-Origin Policy, they cannot include the matching value in their forged requests even though the browser automatically sends the cookie.

// Double submit cookie implementation
class DoubleSubmitCSRF {
  generateCSRFCookie(): string {
    return crypto.randomUUID();
  }

  setCSRFCookie(res: any): string {
    const token = this.generateCSRFCookie();

    res.cookie('csrf-token', token, {
      httpOnly: false,  // Must be readable by JavaScript
      secure: true,
      sameSite: 'strict',
      maxAge: 3600000
    });

    return token;
  }

  validateRequest(req: any): boolean {
    const cookieToken = req.cookies['csrf-token'];
    const headerToken = req.headers['x-csrf-token'];

    if (!cookieToken || !headerToken) {
      return false;
    }

    return cookieToken === headerToken;
  }

  middleware() {
    return (req: any, res: any, next: any) => {
      // Set CSRF cookie if not present
      if (!req.cookies['csrf-token']) {
        this.setCSRFCookie(res);
      }

      // Skip validation for safe methods
      if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
        return next();
      }

      // Validate double submit tokens match
      if (!this.validateRequest(req)) {
        return res.status(403).json({
          error: 'CSRF validation failed',
          message: 'Cookie and header tokens do not match'
        });
      }

      next();
    };
  }
}

// Client-side: Include cookie value in custom header
async function makeProtectedRequest(url: string, data: any) {
  const csrfToken = document.cookie
    .split('; ')
    .find(row => row.startsWith('csrf-token='))
    ?.split('=')[1];

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken || ''
    },
    body: JSON.stringify(data),
    credentials: 'include'
  });

  return response.json();
}

CSRF and Modern Web Applications

Single-page applications (SPAs) built with frameworks like React, Vue, or Angular naturally incorporate CSRF defenses through their API-centric architectures. These applications typically authenticate using bearer tokens stored in JavaScript (localStorage or memory) rather than cookies, avoiding the automatic credential inclusion that enables CSRF attacks. When SPAs do use cookies, they employ CORS-protected APIs requiring custom headers that browsers prevent attackers from setting cross-origin.

// SPA CSRF protection pattern
class SPAAuthService {
  private accessToken: string | null = null;
  private csrfToken: string | null = null;

  async login(username: string, password: string) {
    const response = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password }),
      credentials: 'include' // Include httpOnly cookies
    });

    const data = await response.json();

    // Store bearer token in memory (not localStorage for better security)
    this.accessToken = data.accessToken;

    // Get CSRF token if application uses session cookies
    await this.fetchCSRFToken();
  }

  async fetchCSRFToken() {
    const response = await fetch('/api/csrf-token', {
      credentials: 'include'
    });
    const data = await response.json();
    this.csrfToken = data.csrfToken;
  }

  async authenticatedRequest(url: string, options: RequestInit = {}) {
    const headers = new Headers(options.headers);

    // Include bearer token if available
    if (this.accessToken) {
      headers.set('Authorization', `Bearer ${this.accessToken}`);
    }

    // Include CSRF token for state-changing requests
    if (this.csrfToken && options.method !== 'GET') {
      headers.set('X-CSRF-Token', this.csrfToken);
    }

    return fetch(url, {
      ...options,
      headers,
      credentials: 'include'
    });
  }
}

// Usage in React component
const authService = new SPAAuthService();

async function transferFunds(to: string, amount: number) {
  try {
    const response = await authService.authenticatedRequest('/api/transfer', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ to, amount })
    });

    return await response.json();
  } catch (error) {
    console.error('Transfer failed:', error);
    throw error;
  }
}

GraphQL APIs require special CSRF consideration since they typically use POST requests for all operations including queries. Applications should implement CSRF protection for GraphQL endpoints despite the POST method, either through tokens or custom headers. GraphQL’s introspection features make API structures more discoverable to attackers, increasing the importance of comprehensive CSRF defenses.

CSRF vs CORS Relationship

CSRF and CORS address different security concerns but interact in important ways. CORS controls which origins can make cross-origin requests and access responses, while CSRF protections prevent attackers from triggering unwanted actions using victims’ credentials. CORS does not protect against CSRF attacks—a properly configured CORS policy still allows simple cross-origin requests (GET, POST with certain content types) that can be exploited for CSRF.

Applications relying solely on CORS for security remain vulnerable to CSRF attacks. Attackers can craft simple POST requests with application/x-www-form-urlencoded or text/plain content types that browsers send without CORS preflight checks. These requests include authentication cookies automatically, enabling CSRF attacks despite CORS restrictions. Comprehensive security requires both CORS for controlling cross-origin access and CSRF protections for validating request authenticity.

Best Practices for CSRF Prevention

Implement CSRF tokens for all state-changing operations—POST, PUT, DELETE, PATCH requests modifying server state or triggering actions. GET requests should never trigger state changes, following REST principles that make GET idempotent and safe. Applications violating this principle (changing state via GET) remain inherently vulnerable to CSRF attacks since GET requests require no special protections and execute easily through image tags or links.

Set SameSite cookie attributes to Strict or Lax for all authentication cookies. The Strict setting provides maximum CSRF protection but may impact usability for legitimate cross-site navigation. Lax mode offers balanced protection, allowing cookies with top-level navigation while blocking CSRF attack vectors. Modern browsers default to SameSite=Lax, but explicitly setting this attribute ensures consistent behavior across all browsers and versions.

// Comprehensive CSRF protection configuration
const csrfConfig = {
  // Token-based protection
  tokenHeader: 'X-CSRF-Token',
  tokenFormField: '_csrf',
  tokenLength: 32,
  tokenExpiry: 3600000, // 1 hour

  // Cookie-based protection
  cookieName: 'XSRF-TOKEN',
  cookieOptions: {
    httpOnly: false,     // Readable by JavaScript for double submit
    secure: true,        // HTTPS only
    sameSite: 'strict',  // Strong CSRF protection
    path: '/'
  },

  // Request validation
  safeMethods: ['GET', 'HEAD', 'OPTIONS'],
  requireTokenFor: ['POST', 'PUT', 'DELETE', 'PATCH'],

  // Custom header requirement for API endpoints
  requireCustomHeader: true,
  customHeaderName: 'X-Requested-With',
  customHeaderValue: 'XMLHttpRequest'
};

// Apply multiple CSRF defense layers
class LayeredCSRFProtection {
  validateRequest(req: any): boolean {
    // Layer 1: Check SameSite cookies (browser-level)
    // Handled automatically by browser

    // Layer 2: Require custom header for AJAX requests
    if (csrfConfig.requireCustomHeader) {
      const customHeader = req.headers[csrfConfig.customHeaderName.toLowerCase()];
      if (!customHeader) {
        return false;
      }
    }

    // Layer 3: Validate CSRF token
    const token = req.headers[csrfConfig.tokenHeader.toLowerCase()]
                 || req.body[csrfConfig.tokenFormField];

    if (!this.isValidToken(req.session.id, token)) {
      return false;
    }

    return true;
  }

  isValidToken(sessionId: string, token: string): boolean {
    // Implement token validation logic
    return true; // Placeholder
  }
}

Avoid using GET requests for state-changing operations under any circumstances. URL parameters are logged, cached, and shared easily, making GET-based state changes both insecure and violative of HTTP specifications. Use POST or other appropriate HTTP methods for all actions modifying server state, ensuring these endpoints implement proper CSRF protection.

CSRF and Proxy Services

CorsProxy and similar proxy services do not introduce CSRF vulnerabilities when properly implemented. CSRF attacks target the relationship between browsers and origin servers, exploiting automatic credential inclusion. Proxy services act as intermediaries without accessing or forwarding authentication cookies from user browsers to target sites. Users authenticate to their applications directly, not through proxy services, maintaining the same CSRF attack surface as direct connections.

Using CorsProxy to bypass CORS restrictions while maintaining CSRF protection:

// Using CorsProxy with proper CSRF protection
class SecureAPIClient {
  private csrfToken: string = '';

  async getCsrfToken() {
    // Get CSRF token from your application
    const response = await fetch('https://yourapp.com/api/csrf-token', {
      credentials: 'include'
    });
    const data = await response.json();
    this.csrfToken = data.token;
  }

  async makeProtectedRequest(apiUrl: string, data: any) {
    // Use CorsProxy to bypass CORS
    const proxyUrl = `https://corsproxy.io/?url=${encodeURIComponent(apiUrl)}`;

    // Include CSRF token in request headers
    const response = await fetch(proxyUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': this.csrfToken  // CSRF protection still required!
      },
      body: JSON.stringify(data),
      credentials: 'include'  // Include auth cookies
    });

    return response.json();
  }
}

// Usage maintains both CORS bypass AND CSRF protection
const client = new SecureAPIClient();
await client.getCsrfToken();
await client.makeProtectedRequest('https://api.example.com/transfer', {
  to: 'user123',
  amount: 100
});

However, applications using proxy services must still implement standard CSRF protections. The proxy handles CORS headers enabling cross-origin requests, but CSRF defenses remain the application’s responsibility. Developers should implement CSRF tokens, SameSite cookies, and other protections regardless of whether requests pass through proxies, ensuring comprehensive security across all access patterns.

Important: When using https://corsproxy.io/?url= to access your APIs, always include CSRF tokens in your requests. The proxy solves CORS issues but does not replace CSRF protection mechanisms.

Learn More

Create a free Account to fix CSRF Errors in Production

Say goodbye to CORS errors and get back to building great web applications. It's free!

CORSPROXY Dashboard

Related Terms

More in Security

Related guides

Back to Glossary