CSRF Protection for WordPress: Complete Security Guide
Understand Cross-Site Request Forgery attacks and how WordPress nonces protect your site from unauthorized actions.
Cross-Site Request Forgery (CSRF) tricks authenticated users into performing unwanted actions. WordPress provides built-in protection through nonces, but proper implementation is essential.
Understanding CSRF Attacks
How CSRF Works
- User logs into WordPress admin
- User visits malicious website
- Malicious site sends request to WordPress
- Browser includes authentication cookies
- WordPress processes request as legitimate
CSRF Attack Example
<!-- Malicious site with hidden form -->
<img src="https://yoursite.com/wp-admin/user-new.php?
action=createuser&user_login=hacker&email=hack@evil.com
&role=administrator" style="display:none">
<!-- Or automatic form submission -->
<form action="https://yoursite.com/wp-admin/options.php"
method="POST" id="csrf">
<input name="admin_email" value="hacker@evil.com">
</form>
<script>document.getElementById('csrf').submit();</script>
WordPress Nonce System
What Are Nonces
WordPress nonces are tokens that verify request authenticity. They are user-specific, action-specific, and time-limited.
Creating Nonces
// Generate nonce for specific action
$nonce = wp_create_nonce('my_action_name');
// Add nonce to URL
$url = wp_nonce_url($base_url, 'my_action');
// Add nonce field to form
wp_nonce_field('my_action', 'my_nonce_field');
Verifying Nonces
// Verify nonce in handler
if (!wp_verify_nonce($_POST['my_nonce_field'], 'my_action')) {
wp_die('Security check failed');
}
// For AJAX requests
check_ajax_referer('my_action', 'security');
// Combined capability and nonce check
if (!current_user_can('manage_options') ||
!wp_verify_nonce($_POST['nonce'], 'admin_action')) {
wp_die('Unauthorized');
}
AJAX CSRF Protection
JavaScript Implementation
// Localize nonce for AJAX
wp_localize_script('my-script', 'wpfsAjax', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wpfs_ajax_nonce')
));
// JavaScript AJAX call
jQuery.ajax({
url: wpfsAjax.ajaxurl,
type: 'POST',
data: {
action: 'my_ajax_action',
security: wpfsAjax.nonce,
// other data
}
});
PHP AJAX Handler
add_action('wp_ajax_my_ajax_action', 'handle_ajax');
function handle_ajax() {
// Verify nonce first
check_ajax_referer('wpfs_ajax_nonce', 'security');
// Process request
$result = do_something();
wp_send_json_success($result);
}
Form Protection Patterns
Complete Form Example
<form method="POST" action="">
<?php wp_nonce_field('save_settings', 'settings_nonce'); ?>
<input type="text" name="option_value">
<button type="submit" name="submit_settings">Save</button>
</form>
<?php
if (isset($_POST['submit_settings'])) {
if (!wp_verify_nonce($_POST['settings_nonce'], 'save_settings')) {
wp_die('Invalid request');
}
// Process form safely
}
?>
Additional CSRF Protections
SameSite Cookies
- WordPress 5.2+ sets SameSite=Lax by default
- Prevents cookies being sent with cross-site requests
- Additional browser-level protection
Referrer Validation
// Additional referrer check
function validate_referrer() {
$referrer = wp_get_referer();
if (!$referrer ||
strpos($referrer, home_url()) !== 0) {
wp_die('Invalid referrer');
}
}
Common Mistakes
- Using GET for state-changing operations
- Not verifying nonces in handlers
- Reusing nonces across different actions
- Exposing nonces in cacheable content
Conclusion
CSRF protection through WordPress nonces is essential for secure forms and AJAX requests. Always verify nonces server-side and combine with capability checks for robust security.
Written by Sarah Chen
WP Folder Shield Team