How to Protect Against CSRF Attacks on WordPress

""

Cross-Site Request Forgery (CSRF) is a security vulnerability that allows attackers to trick users into performing actions on a website without their knowledge or consent. This is a serious threat, especially for websites that process sensitive data or transactions. 

To protect against CSRF attacks, use nonces in your WordPress code.

Note: We understand the term “nonce” is a slang term in the UK, and may be updated in the future. In this case, we are only referring to the current technical term.

What are nonces?

A nonce is a one-time use token generated for each user session. The nonce can be included in a form, URL, or AJAX request and is used to verify the legitimacy of that request. 

Helping combat CSRF attacks, a nonce ensures the form action being taken was intended by the user. It confirms the request is legitimate and can’t be manipulated by attackers..

When validating a nonce, be careful of the operator you use in your conditional. At a glance, the following appears safe but is easily exploitable by submitting any nonce value:

// ❌ incorrect nonce validation.
if ( ! isset( $_POST['_wpnonce'] ) && ! wp_verify_nonce( $_POST['_wpnonce'], 'update-post' )  ) {
  return;
}

Instead, a correct validation would look like this:


if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'update-post' ) ) {
return;
}

Using nonces in WordPress VIP

Anyone coding for WordPress VIP should vigilantly use validating, sanitizing, and escaping to securely handle data incoming to WordPress, and to present it to the end user. To protect against CSRF attacks, use nonces to validate all form submissions.

There are three main ways to incorporate nonces into WordPress VIP code: via URL, form, and AJAX.

1. URL nonces

To add a nonce to a URL, use the wp_nonce_url() function. This function generates a URL that includes a nonce, which can be verified using the wp_verify_nonce function.

URL nonce code example:

<?php
// Create a menu page for the demo nonce URL link.
add_action( 'admin_menu', function() {
	add_menu_page(
		'URL Nonce Example',
		'URL Nonce Example',
		'publish_posts',
		'vip__nonce_url_link',
		'vip__nonce_url_link_callback'
	);
} );

// Contents for the demo nonce URL page.
function vip__nonce_url_link_callback () {
	/**
	 * Verify the nonce URL exists & is legitimate.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_verify_nonce
	 */
	if ( isset( $_GET['_vip__nonce'] ) && wp_verify_nonce( $_GET['_vip__nonce'], 'vip__nonce_action' ) ) {
		?>
		<p>✅ Valid nonce.</p>
		<?php
		return;
	}

	$url = admin_url('options.php?page=vip__nonce_url_link');

	/**
	 * Create a nonce URL.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_nonce_url
	 */
	$nonce_url = wp_nonce_url( $url, 'vip__nonce_action', '_vip__nonce' );
	?>
	<a href="<?php echo esc_url( $nonce_url ); ?>" class="button button-primary">Nonce URL »</a>
	<?php
}

2. Form nonces

To add a nonce to a form, use the wp_nonce_field() function. This function generates a hidden form field that includes a nonce, which can be verified using the check_admin_referer(), check_ajax_referer(), or wp_verify_nonce() functions. 

Note: Using check_admin_referer() without the action argument, or check_ajax_referer() with the last argument set to false, can lead to CSRF bypass.

Form nonce code example:

<?php
// Add custom metabox to `post` post types.
add_action( 'add_meta_boxes', function () {
	add_meta_box(
		'vip__metabox_nonce_example',
		'Example Field',
		'vip__custom_post_metabox',
		'post'
	);
} );

function vip__custom_post_metabox( $post ) {
	/**
	 * Create a hidden nonce field, along side a custom form field.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_nonce_field
	 */
	wp_nonce_field( 'vip__metabox_checkbox', '_vip__nonce' );
	?>
	<label>
		<input type='checkbox' name='vip__checkbox' />
	</label>
	<?php
}

add_action( 'save_post', function ( $post_id, $post ) {

	if ( 'post' !== $post->post_type ) {
		return;
	}
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return;
	}
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return;
	}
	/**
	 * Verify the nonce exists & verify it's correct.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_verify_nonce
	 */
	if ( ! isset( $_POST['_vip__nonce'] ) || ! wp_verify_nonce( $_POST['_vip__nonce'], 'vip__metabox_checkbox' ) ) {
		return;
	}

	// valid request, do something with `$_POST['vip__checkbox']`.

}, 10, 2 );

3. AJAX nonces

To add a nonce to an AJAX request, include the nonce in the data sent to the server. Verify the nonce on the server-side using the wp_verify_nonce() or check_ajax_referer() function. Caution: using the latter verification method with the last argument set to false can lead to CSRF bypass.

AJAX nonce code example:

<?php
// Create a menu page for the demo AJAX script.
add_action( 'admin_menu', function() {
	add_menu_page(
		'AJAX Nonce Example',
		'AJAX Nonce Example',
		'publish_posts',
		'vip__ajax_form',
		'vip__ajax_form_callback'
	);
} );

// Contents for the demo menu page.
function vip__ajax_form_callback() {
	/**
	 * Create the nonce for the admin page, including the action name for context.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_create_nonce
	 */
	$ajax_nonce = wp_create_nonce( "vip__ajax_form_nonce" );
	?>
	<h2>AJAX Nonce</h2>
	<input type='submit' class='button button-primary' id='click_me' value='Send AJAX Post Request »'>
	<pre id='vip__ajax_form_output'></pre>

	<script type="text/javascript">
		jQuery(document).ready( function($) {
			var data = {
				action: 'vip__ajax_form_action',
				_vip__nonce: <?php echo wp_json_encode( $ajax_nonce ); ?>,
				my_string: 'Freedom to Publish!'
			};
			$('#click_me').click(function() {
				// Post the request to WordPress' AJAX URL.
				$.post( ajaxurl, data, function( response ) {
					$('#vip__ajax_form_output').text("Response: " + response.data);
				});
			});
		});
	</script>
	<?php
}

// The AJAX hook for the demo forms POST request.
add_action( 'wp_ajax_vip__ajax_form_action', function() {
	/**
	 * Verify the nonce exists & and is correct.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_verify_nonce
	 */
	if ( ! isset( $_POST['_vip__nonce'] ) || ! wp_verify_nonce( $_POST['_vip__nonce'], 'vip__ajax_form_nonce' ) ) {
		wp_send_json_error( 'invalid nonce' );
	}
	if ( ! current_user_can( 'publish_posts' ) ) {
		wp_send_json_error( 'invalid permissions' );
	}

	$data   = esc_html( sanitize_text_field( $_POST['my_string'] ) );
	$return = '✅ Nonce is valid! Data is: ' . $data;

	wp_send_json_success( [ $return ] );
});

<?php
// Create a menu page for the demo AJAX script.
add_action( 'admin_menu', function() {
	add_menu_page(
		'AJAX Nonce Example',
		'AJAX Nonce Example',
		'publish_posts',
		'vip__ajax_form',
		'vip__ajax_form_callback'
	);
} );

// Contents for the demo menu page.
function vip__ajax_form_callback() {
	/**
	 * Create the nonce for the admin page, including the action name for context.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_create_nonce
	 */
	$ajax_nonce = wp_create_nonce( "vip__ajax_form_nonce" );
	?>
	<h2>AJAX Nonce</h2>
	<input type='submit' class='button button-primary' id='click_me' value='Send AJAX Post Request »'>
	<pre id='vip__ajax_form_output'></pre>

	<script type="text/javascript">
		jQuery(document).ready( function($) {
			var data = {
				action: 'vip__ajax_form_action',
				_vip__nonce: <?php echo wp_json_encode( $ajax_nonce ); ?>,
				my_string: 'Freedom to Publish!'
			};
			$('#click_me').click(function() {
				// Post the request to WordPress' AJAX URL.
				$.post( ajaxurl, data, function( response ) {
					$('#vip__ajax_form_output').text("Response: " + response.data);
				});
			});
		});
	</script>
	<?php
}

// The AJAX hook for the demo forms POST request.
add_action( 'wp_ajax_vip__ajax_form_action', function() {
	/**
	 * Verify the nonce exists & and is correct.
	 *
	 * @see https://codex.wordpress.org/Function_Reference/wp_verify_nonce
	 */
	if ( ! isset( $_POST['_vip__nonce'] ) || ! wp_verify_nonce( $_POST['_vip__nonce'], 'vip__ajax_form_nonce' ) ) {
		wp_send_json_error( 'invalid nonce' );
	}
	if ( ! current_user_can( 'publish_posts' ) ) {
		wp_send_json_error( 'invalid permissions' );
	}

	$data   = esc_html( sanitize_text_field( $_POST['my_string'] ) );
	$return = '✅ Nonce is valid! Data is: ' . $data;

	wp_send_json_success( [ $return ] );
});

Using nonces in WordPress VIP helps protect against CSRF attacks

Using nonces in your WordPress code is an important step in protecting your website from CSRF attacks. By verifying that requests are legitimate and coming from the same user who initiated the session, nonces help secure your website and the privacy of your users.

Learn how to raise your WordPress security to the next level and get a free demo to see how WordPress VIP can keep your site secure.

Get the latest content updates

Want to be notified about new content?

Leave your email address and we’ll make sure you stay updated.