Jump to content

Easy CSRF protection for AJAX requests everywhere

Recommended Posts

There is already some information on how to do this but it's a bit scattered, so here's a quick tutorial...

This is how $session->CSRF->hasValidToken() handles AJAX requests, by getting the token name and value from request headers:

if($this->config->ajax && isset($_SERVER["HTTP_X_$tokenName"]) && $_SERVER["HTTP_X_$tokenName"] === $tokenValue) {
	$valid = true;
} else if($this->input->post($tokenName) === $tokenValue) {
	$valid = true; 

So all we need to do is to pass the name and value to our JavaScript and set the right headers with our AJAX request.

We could instead of setting headers put the name and value pair into the body of a POST request, as the second conditional doesn't care if the POST request is received via AJAX or not. But this limits the request to POST and the body data type to either multipart/form-data or application/x-www-form-urlencoded (because that's all that PHP's $_POST superglobal will handle). Admittedly this might be 99% of use cases, but for example if a GET request invokes significant processing then CSRF protection could be used to prevent a DOS attack on the endpoint. So, for the sake of a bit more flexibility, and to be more in keeping with what PW expects, we'll use request headers.

I find it neater to write the token name and value into HTML and retrieve that using JavaScript DOM functions, instead of writing JavaScript with PHP 😝. So somewhere, maybe in init.inc.php, get the token name and value:

$csrfTokenName = $session->CSRF->getTokenName();
$csrfTokenValue = $session->CSRF->getTokenValue();

Then in a global footer template:

<div class="js-csrf-token" style="display:none;" data-csrf-token-name="<?php echo $csrfTokenName; ?>" data-csrf-token-value="<?php echo $csrfTokenValue; ?>"></div>

Now to retrieve the token in JavaScript:

function getCsrfToken() {
	const csrfTokenNode = document.getElementsByClassName('js-csrf-token')[0];

	return {
		name: csrfTokenNode.getAttribute('data-csrf-token-name'),
		value: csrfTokenNode.getAttribute('data-csrf-token-value'),

Then in any AJAX request:

const csrfToken = getCsrfToken();
let xhr = new XMLHttpRequest();

// ...
// open the request and do some configuration
// ...

xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // tell the server and PW that it's an AJAX request
xhr.setRequestHeader('X-' + csrfToken.name, csrfToken.value); // create a header with the token

// ...
// send the request
// ...

And that's all, in the endpoint just do...

if ($session->CSRF->hasValidToken()) {
	// works for AJAX!

This has used the default CSRF token created for every session, but to create and use a different token just specify an id:

$csrfTokenName = $session->CSRF->getTokenName('myAjaxToken'); // this creates a token if one with the given id doesn't exist
$csrfTokenValue = $session->CSRF->getTokenValue('myAjaxToken');


if ($session->CSRF->hasValidToken('myAjaxToken') {
// ...


Edited by ren
  • Like 9
  • Thanks 5

Share this post

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Create New...