Protecting Web Applications with CSRF Tokens: A Complete Guide with Code
Modern web applications require advanced security measures to protect user data. One of the most common threats is the Cross-Site Request Forgery (CSRF) attack. In this article, we’ll explore what CSRF tokens are, why they are essential, and how to implement them correctly in PHP code, following the best security practices.
What Is a CSRF Token?
A CSRF Token is a unique token generated by the server and associated with the user’s session, which is sent along with every sensitive request, such as form submissions. When the server receives a POST request, it compares the token sent with the one stored in the session. If the tokens match, the request is considered authentic.
Why Use a CSRF Token?
CSRF attacks occur when an authenticated user unintentionally makes a request to a web application. Using CSRF tokens prevents malicious sites from sending requests on behalf of the user without their consent, thus protecting the user’s data and actions.
How Does a CSRF Token Work?
- Token Generation: The server generates a unique token and associates it with the user’s session.
- Integration into the HTML Form: The token is inserted as a hidden field in the form.
- Token Verification: When the user submits the form, the server compares the token sent with the one stored in the session.
Let’s see how to implement the CSRF token in a PHP application, ensuring we follow the best security practices.
Implementing the CSRF Token in PHP
1. Create a CSRF Token
In the PHP file that handles the form, add code to generate a CSRF token, save it in the user’s session, and set an expiration time.
<?php
session_start();
// Set security options for the session
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // Ensure the site uses HTTPS
ini_set('session.use_strict_mode', 1);
// Regenerate the session ID to prevent session fixation attacks
session_regenerate_id(true);
// Generate a CSRF token if it doesn't exist
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$_SESSION['csrf_token_time'] = time(); // Save the creation timestamp
}
?>
The generated token is a secure random string. This string is stored in $_SESSION['csrf_token'] and can be retrieved anywhere in the code. We’ve also saved the creation timestamp in $_SESSION['csrf_token_time to set an expiration for the token.
2. Insert the Token into the HTML Form
Once generated, insert the token as a hidden field within the HTML form. This way, the token will be sent along with the form data.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form with CSRF Protection</title>
</head>
<body>
<h1>Submit Data Securely</h1>
<form method="POST" action="process_form.php">
<!-- Hidden field for the CSRF token -->
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8'); ?>">
<!-- Other form fields -->
<label for="name">Name:</label>
<input type="text" name="name" id="name" required>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required>
<button type="submit">Submit</button>
</form>
</body>
</html>
Here, the CSRF token is inserted as a hidden input called csrf_token. We use htmlspecialchars to prevent XSS attacks. When the user submits the form, this field will be included in the POST request data.
3. Verify the CSRF Token
In the PHP file that handles the form submission (e.g., process_form.php), verify the token sent by comparing it with the one stored in the session, check its expiration, and handle errors securely.
<?php
session_start();
// Set security options for the session
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // Ensure the site uses HTTPS
ini_set('session.use_strict_mode', 1);
// Verify that the request is a POST request
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Check if the CSRF token is present in the request and in the session
if (isset($_POST['csrf_token'], $_SESSION['csrf_token'])) {
// Check if the token has not expired (e.g., valid for 5 minutes)
$max_time = 300; // 300 seconds = 5 minutes
if (time() - $_SESSION['csrf_token_time'] <= $max_time) {
// Verify that the token matches
if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
// Token is valid; proceed with form processing
echo "Form submitted successfully!";
// Perform other operations with the form data
// Regenerate the token after use
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$_SESSION['csrf_token_time'] = time();
} else {
// Invalid CSRF token; handle the error
header('HTTP/1.1 400 Bad Request');
echo "An error occurred with your request. Please try again.";
exit;
}
} else {
// Token expired; handle the error
header('HTTP/1.1 400 Bad Request');
echo "The CSRF token has expired. Please reload the page and try again.";
exit;
}
} else {
// Missing token; handle the error
header('HTTP/1.1 400 Bad Request');
echo "An error occurred with your request. Please try again.";
exit;
}
}
?>
The hash_equals() function allows us to compare the two strings securely, preventing timing attacks. We also verify that the token has not expired. In case of an error, we return a generic message without providing technical details that could be useful to an attacker.
4. Organize Code with Functions
To make the code cleaner and more reusable, create functions for generating and verifying the CSRF token.
<?php
session_start();
function generateCsrfToken() {
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_token_time'] = time();
return $token;
}
function verifyCsrfToken($token) {
if (!isset($_SESSION['csrf_token'], $_SESSION['csrf_token_time'])) {
return false;
}
// Check expiration
$max_time = 300; // 5 minutes
if (time() - $_SESSION['csrf_token_time'] > $max_time) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
?>
You can then use these functions in your code to generate and verify the token more easily.
Security Considerations
- Regenerate the Token After Use: After successfully verifying the CSRF token and processing the form, regenerate the token to prevent replay attacks.
- Set an Expiration for the Token: Limit the attacker’s window of opportunity by setting an expiration time for the CSRF token.
- Protect the Session: Use
session_regenerate_id(true)after authentication and set session security parameters likesession.cookie_httponlyandsession.cookie_secure. - Use HTTPS: It’s crucial that web applications use HTTPS to encrypt communication and prevent token interception.
- Handle Errors Securely: Avoid using
die()with detailed error messages. Instead, provide generic messages and redirect the user if necessary. - Verify Token Existence in Session: Ensure that the token exists both in the request and in the session to avoid errors and potential exploits.
- Support for Applications with Multiple Forms: If your application has multiple forms or protected actions, consider implementing unique CSRF tokens for each, or include an identifier in the token’s name.
- Considerations for AJAX Requests: If you use AJAX for POST requests, remember to include the CSRF token in these requests as well, for example, by inserting it in the data or headers of the request.
- Testing and Validation: Implement unit or integration tests to verify that the CSRF protection works as intended and that there are no vulnerabilities.
Complete Example
Here is the complete implementation to generate, include, and verify a CSRF token in a PHP application.
index.php – Page with the form
<?php
session_start();
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.use_strict_mode', 1);
// Function to generate the CSRF token
function generateCsrfToken() {
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_token_time'] = time();
return $token;
}
// Generate a CSRF token if it doesn't exist
if (empty($_SESSION['csrf_token'])) {
generateCsrfToken();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form with CSRF Token</title>
</head>
<body>
<form method="POST" action="process_form.php">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8'); ?>">
<label for="name">Name:</label>
<input type="text" name="name" id="name" required>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required>
<button type="submit">Submit</button>
</form>
</body>
</html>
process_form.php – Page for verification and processing
<?php
session_start();
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.use_strict_mode', 1);
// Function to verify the CSRF token
function verifyCsrfToken($token) {
if (!isset($_SESSION['csrf_token'], $_SESSION['csrf_token_time'])) {
return false;
}
// Check expiration
$max_time = 300; // 5 minutes
if (time() - $_SESSION['csrf_token_time'] > $max_time) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
// Verify POST request
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['csrf_token'])) {
if (verifyCsrfToken($_POST['csrf_token'])) {
// Token is valid; proceed with data processing
echo "Form submitted successfully!";
// Additional actions on the form data
// Regenerate the token after use
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
$_SESSION['csrf_token_time'] = time();
} else {
// Invalid or expired CSRF token
header('HTTP/1.1 400 Bad Request');
echo "An error occurred with your request. Please try again.";
exit;
}
} else {
// Missing CSRF token
header('HTTP/1.1 400 Bad Request');
echo "An error occurred with your request. Please try again.";
exit;
}
}
?>
Conclusion
CSRF tokens are one of the most effective methods to protect web applications from CSRF attacks. With this guide, you’ve learned how to generate, insert, and verify CSRF tokens in a PHP application, following the best security practices. By implementing these techniques, you will add an essential layer of protection to your web applications, ensuring the security of user data and the integrity of operations.
Further Advice
- Use HTTPS: Ensure that your application always uses secure connections (HTTPS) to protect CSRF tokens and other sensitive data during transmission.
- Protection Against XSS: Since an XSS attack can compromise CSRF tokens, make sure to also protect the application against Cross-Site Scripting attacks.
- Session Management: Keep session security settings up to date and consider using encrypted sessions for additional protection.
- Monitoring and Logging: Implement monitoring systems to quickly detect and respond to any attack attempts.
By implementing these practices, you can further enhance the security of your web application and effectively protect users from CSRF attacks.
