PHP PDF Generation: Which Library Should You Use?

12 min read 2,280 words
PDF Generation in PHP: Which Library to Use and When featured image

CSRF Protection in PHP Forms: Why Every Form Needs a Token

Cross-Site Request Forgery attacks trick users into submitting forms they did not intentionally submit. The attack works because web browsers automatically send cookies with every request to a domain, including requests initiated from malicious websites. A CSRF token breaks this automatic behaviour by requiring a secret value that only your legitimate form can provide. Without that token, the server rejects the submission.

PHP applications that process form data without CSRF protection are vulnerable. This applies to login forms, contact forms, admin panels, and any functionality that changes data or performs actions on behalf of a logged-in user. The risk is real for any business running PHP-based websites or web applications in the UK or elsewhere.

How CSRF Tokens Work in PHP

A CSRF token is a random string generated by your server and tied to the user's session. When you display a form, you include the token as a hidden field. When the form is submitted, you check that the submitted token matches the one stored in the session. If it matches, the request is legitimate. If it does not, you reject the request.

The token must be unpredictable. Using timestamps, user IDs, or other guessable values creates a weakness that attackers can exploit. Cryptographically secure random generation is essential.

Generating a CSRF Token in PHP

Store the token in the session at the start of each session. Generate it using random_bytes(), which provides cryptographically secure randomness. Convert the bytes to a hexadecimal string for easy handling.

session_start();

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

$token = $_SESSION['csrf_token'];

Place the token in your form using a hidden input field.

<form method="POST" action="process.php">
    <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($token, ENT_QUOTES, 'UTF-8'); ?>">
    <label for="name">Name</label>
    <input type="text" name="name" id="name">
    <button type="submit">Submit</button>
</form>

Always escape the token value with htmlspecialchars() to prevent XSS attacks from breaking your CSRF protection.

Validating the CSRF Token on Submission

When processing the form, check that the submitted token exists and matches the session token exactly. Use hash_equals() for the comparison to prevent timing attacks.

session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_SESSION['csrf_token']) || !isset($_POST['csrf_token'])) {
        http_response_code(400);
        exit('Invalid request');
    }

    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
        http_response_code(400);
        exit('Invalid request');
    }

    // Process the form safely here
}

Always check the request method first. Reject non-POST requests that reach your form processing logic.

Why hash_equals Matters

The hash_equals() function compares two strings in constant time. Regular string comparison in PHP can leak information through timing differences, allowing attackers to guess the token one character at a time. This type of attack is called a timing side-channel attack. Using hash_equals() closes that gap.

// Insecure: timing differences can leak token information
if ($_SESSION['csrf_token'] == $_POST['csrf_token'])

// Secure: constant-time comparison prevents timing attacks
if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token']))

For any PHP application handling sensitive operations, constant-time comparison is not optional. It is a basic requirement for proper token validation.

Protecting AJAX Requests with CSRF Tokens

JavaScript-driven forms and AJAX submissions need CSRF protection too. The approach is slightly different. Instead of relying on form submissions alone, you include the token in a custom HTTP header and verify it server-side.

First, make the token available to JavaScript. You can echo it into a data attribute on the page or inline it in a script tag.

<script>
    const csrfToken = "<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8'); ?>";
</script>

When making an AJAX request, include the token in the headers.

fetch('/api/submit', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify({ name: 'example' })
});

On the server, retrieve the header and validate it alongside the session token.

if (!isset($_SERVER['HTTP_X_CSRF_TOKEN']) ||
    !hash_equals($_SESSION['csrf_token'], $_SERVER['HTTP_X_CSRF_TOKEN'])) {
    http_response_code(403);
    exit('Forbidden');
}

Note that PHP accesses custom headers by prefixing them with HTTP_ and converting hyphens to underscores. X-CSRF-Token becomes HTTP_X_CSRF_TOKEN.

Common CSRF Implementation Mistakes

Several mistakes appear frequently in PHP form implementations. Avoiding them matters for the security of your application.

  • Using predictable token sources: Relying on uniqid(), timestamps, or user IDs makes tokens guessable. Always use random_bytes() with at least 32 bytes of output.
  • Skipping server-side validation: Client-side checks provide no protection against CSRF attacks. The server must validate every token.
  • Storing tokens in cookies: Cookies are automatically sent with every request, which defeats the purpose of CSRF protection. Tokens belong in the session, not in cookies.
  • Not regenerating tokens after validation: Reusing the same token across multiple form submissions can allow replay attacks. In high-security applications, consider regenerating the token after each successful validation.
  • Checking only that the token exists: An attacker can read the token from your page and submit it. The check must verify that the submitted token matches the session token exactly.

Session Security and CSRF Protection

CSRF tokens depend on secure session handling. If your sessions are insecure, your CSRF protection is weakened. Several session settings affect the overall security of your PHP application.

Enable secure session cookies in your PHP configuration or at runtime. This prevents sessions from being transmitted over unencrypted connections.

session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => '',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);
session_start();

The samesite attribute set to Strict or Lax provides additional protection against CSRF attacks at the browser level. Strict is more secure but can affect legitimate navigation. Lax is a reasonable default for most applications.

Regenerating the session ID periodically reduces the risk of session fixation attacks, where an attacker sets a user's session ID to a known value.

session_regenerate_id(true);

The true parameter deletes the old session, preventing the old ID from being used.

When Building Your Own vs Using a Framework

PHP developers can implement CSRF protection from scratch, as shown in this guide. For simple projects, a custom implementation covers the basics. For larger applications with complex form logic, using an established framework or library is usually the better choice.

Frameworks like Laravel and Symfony have well-tested CSRF implementations that handle edge cases automatically. They manage token generation, validation, and regeneration without additional work from the developer. If your project uses a framework, rely on its built-in CSRF protection rather than replacing it.

If you build custom forms, keep the implementation straightforward. Complex CSRF logic across many files is harder to audit and easier to make mistakes with. A simple, consistent approach applied everywhere is more reliable than a sophisticated solution in one place and nothing in another.

Testing Your CSRF Protection

Verification matters as much as implementation. Test that your CSRF protection works correctly before deploying any form.

Submit a form without the token and confirm the server rejects it with a 400 or 403 response. Submit a form with an incorrect token and confirm rejection. Submit a form with the correct token and confirm the request succeeds.

Automated tools can help test for CSRF vulnerabilities. OWASP ZAP and similar scanners check whether forms include tokens and whether token validation is enforced. Use these tools as part of your regular security testing, especially after adding new features or modifying existing forms.

Keep in mind that CSRF is one layer of web application security. Proper protection also requires addressing cross-site scripting (XSS), ensuring secure authentication, and configuring your server securely.

CSRF Protection and PHP Versions

Using a supported PHP version matters for security. Older PHP versions lack security updates and may not include functions like random_bytes() and hash_equals() that are essential for secure CSRF implementation.

PHP 7.0 and later include both functions. If you are running an older version, upgrade first. Security features built into the language make secure implementation easier and more reliable.

Check your current PHP version regularly. Hosting providers sometimes default to older versions, and applications may depend on deprecated features. Keeping PHP updated is part of maintaining a secure application.

CSRF Tokens and Third-Party Integrations

If your PHP application integrates with external services, payment gateways, or APIs, those integrations may have their own CSRF requirements. Some services use OAuth tokens instead of session-based tokens. Others rely on state parameters in authorization requests.

Understand how each integration expects to receive and validate tokens. Mixing different token strategies can create gaps in your protection if not handled carefully.

If your application acts as an OAuth client, store the state parameter returned during authorization and verify it when the provider redirects back. This prevents CSRF attacks on the authorization flow.

Regular Security Reviews for PHP Applications

CSRF protection is not a one-time implementation. Applications evolve, new forms are added, and code changes can inadvertently break or bypass existing protections. Regular reviews help catch these issues.

When adding new forms to your application, apply CSRF protection consistently from the start. Retrofitting protection into existing forms is error-prone and harder to test thoroughly.

For business websites handling customer data or performing transactions, a periodic security review is worth considering. This can be part of broader website maintenance and helps ensure that protections remain in place as the application changes.

If you are building or maintaining a security certification for your organisation, CSRF protection will likely be part of the technical controls your assessor reviews.

Related practical reading

These related guides can help you connect this topic with the wider website, server, security, and support decisions around it.

Building Secure PHP Forms Takes Attention to Detail

CSRF protection is one of those technical details that matters more as an application grows. A single unprotected form can become a serious risk if the application later gains authenticated users or handles sensitive operations. Adding tokens during initial development costs less than retrofitting them later.

The key requirements are straightforward: generate tokens securely, include them in every form, validate them on every submission, and use constant-time comparison during validation. These steps, applied consistently, stop most CSRF attacks.

For PHP applications used by UK businesses or handling UK user data, security best practices are part of responsible data handling. CSRF protection is a baseline requirement, not an optional extra.

If you are reviewing an existing PHP application and are unsure whether CSRF protection is properly implemented, a focused technical review can identify gaps. Understanding what is in place and what needs fixing is the practical first step.

Frequently Asked Questions

Is CSRF protection necessary for read-only forms?
Forms that only read data and do not change anything typically do not need CSRF tokens. However, if the form interacts with sessions, triggers logging, or performs any action on the server, a token adds a layer of protection. When in doubt, include the token. It adds minimal overhead and prevents future vulnerabilities if the form is later extended to perform actions.
Can CSRF tokens expire?
Tokens stored in PHP sessions expire when the session expires, which depends on your session lifetime settings. For high-security applications, you can add an explicit token expiry timestamp and validate it server-side. This limits the window during which a stolen token could be used. In most standard PHP applications, session-based expiry is sufficient.
What happens if a user opens the same form in multiple tabs?
If the token is stored in the session and not regenerated between page loads, the same token works across multiple tabs. This is the expected behaviour for most applications. If you regenerate the token on each page load, the token in the first tab becomes invalid when the user opens a second tab. Neither approach is wrong, but consistency matters. Choose one and apply it uniformly.
Can I use the same token for all forms in my application?
Yes, a single token per session is common and sufficient for most applications. Each form submission validates against the same session token. Some implementations use separate tokens per form action to limit the scope of a compromised token, but this adds complexity. For standard business applications, one token per session is practical and secure when implemented correctly.
Do I need CSRF protection for API endpoints that accept JSON?
Yes, API endpoints are just as vulnerable to CSRF as HTML forms. If your API processes state-changing requests using cookies for authentication, it needs CSRF protection. Include the token in a custom header like X-CSRF-Token rather than in the request body, since cross-origin requests cannot set custom headers without CORS permission.
What should I do if I find a CSRF vulnerability in my live application?
Fix it as soon as possible. Add token generation to your forms and validation to your processing logic. If the vulnerability has been present for a while, consider whether any suspicious submissions may have occurred. For business applications, document the issue and the fix for your records. If customer data may have been affected, follow your incident response process and consider notifying relevant parties as appropriate.
Should I handle CSRF protection differently for a small business website?
The principles are the same regardless of site size. A small business contact form, an admin login, or a newsletter signup all benefit from CSRF tokens. For small businesses without dedicated technical staff, using a well-maintained CMS or framework with built-in CSRF protection reduces the risk of implementation mistakes. Regular maintenance and updates remain important for keeping protection effective over time.