DEV Community

Parzival
Parzival

Posted on • Edited on

CSRF Protection in PHP

What is CSRF?

Cross-Site Request Forgery (CSRF) is a web security vulnerability that allows an attacker to trick authenticated users into executing unwanted actions on a website where they're currently logged in. The attack works by exploiting the trust that a website has in a user's browser.

How CSRF Attacks Work

  1. User logs into legitimate website A and receives a session cookie
  2. User visits malicious website B while still logged into A
  3. Website B contains code that makes a request to website A
  4. The browser automatically includes the session cookie
  5. Website A processes the request thinking it's legitimate

CSRF Protection Methods in PHP

1. Token-Based Protection Using Hidden Input

This is the most common method. Here's how to implement it:

// In your session initialization (e.g., at login)
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// In your form
function generateFormWithCSRFToken() {
    return '<form method="POST" action="/submit">
        <input type="hidden" name="csrf_token" value="' . $_SESSION['csrf_token'] . '">
        <!-- rest of your form fields -->
        <input type="submit" value="Submit">
    </form>';
}

// In your form processing
function validateCSRFToken() {
    if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
        !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        die('CSRF token validation failed');
    }
    return true;
}
Enter fullscreen mode Exit fullscreen mode

2. CSRF Protection Using Custom Headers

This method uses AJAX requests with custom headers:

// PHP Backend
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Validate the token
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $headers = getallheaders();
    if (!isset($headers['X-CSRF-Token']) || 
        !hash_equals($_SESSION['csrf_token'], $headers['X-CSRF-Token'])) {
        http_response_code(403);
        die('CSRF token validation failed');
    }
}

// JavaScript Frontend
const csrfToken = '<?php echo $_SESSION["csrf_token"]; ?>';

fetch('/api/endpoint', {
    method: 'POST',
    headers: {
        'X-CSRF-Token': csrfToken,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
});
Enter fullscreen mode Exit fullscreen mode

3. Double Submit Cookie Pattern

This method involves sending the token both as a cookie and as a request parameter:

// Set both cookie and session token
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
setcookie('csrf_token', $token, [
    'httponly' => true,
    'secure' => true,
    'samesite' => 'Strict'
]);

// Validation function
function validateDoubleSubmitToken() {
    if (!isset($_COOKIE['csrf_token']) || 
        !isset($_POST['csrf_token']) || 
        !isset($_SESSION['csrf_token'])) {
        return false;
    }

    return hash_equals($_COOKIE['csrf_token'], $_POST['csrf_token']) && 
           hash_equals($_SESSION['csrf_token'], $_POST['csrf_token']);
}
Enter fullscreen mode Exit fullscreen mode

4. SameSite Cookie Attribute

Modern applications can also use the SameSite cookie attribute as an additional layer of protection:

// Set cookie with SameSite attribute
session_start();
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => $_SERVER['HTTP_HOST'],
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);
Enter fullscreen mode Exit fullscreen mode

Best Practices for CSRF Protection

  1. Token Generation
    • Use cryptographically secure random number generators
    • Make tokens sufficiently long (at least 32 bytes)
    • Generate new tokens for each session
function generateSecureToken($length = 32) {
    return bin2hex(random_bytes($length));
}
Enter fullscreen mode Exit fullscreen mode
  1. Token Validation
    • Use timing-safe comparison functions
    • Validate token presence and value
    • Implement proper error handling
function validateToken($userToken, $storedToken) {
    if (empty($userToken) || empty($storedToken)) {
        return false;
    }
    return hash_equals($storedToken, $userToken);
}
Enter fullscreen mode Exit fullscreen mode
  1. Form Implementation
    • Include tokens in all forms
    • Implement automatic token injection
    • Handle token rotation
class CSRFProtection {
    public static function getTokenField() {
        return sprintf(
            '<input type="hidden" name="csrf_token" value="%s">',
            htmlspecialchars($_SESSION['csrf_token'])
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Framework-Specific Protection

Many PHP frameworks provide built-in CSRF protection:

Laravel Example

// In your form
@csrf

// Manual token generation
{{ csrf_field() }}
Enter fullscreen mode Exit fullscreen mode

Symfony Example

// In your form
{{ csrf_token('form_name') }}
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls to Avoid

  1. Don't use predictable tokens
  2. Don't store tokens in JavaScript variables accessible globally
  3. Don't skip CSRF protection for AJAX requests
  4. Don't rely solely on checking the Referer header
  5. Don't use the same token for multiple forms

Remember that CSRF protection should be part of a broader security strategy that includes proper session management, secure cookie handling, and input validation.

Top comments (0)