While developing a secure PHP web application, we want to ensure all actions are authorized and requests are honored only when they’re received from our trusted end-users. A hacker can put together a URL with GET form data and somehow forwards it to the user, which can result in commands being executed using the credentials that the hacker has patched, and we want to make sure that won’t happen that’s why we can make use of PHP nonce and tokenize our web forms and URLs.

CSRF nonce – the idea

The idea of using a CSRF token is to validate the request (GET, POST, etc) to extract a nonce that we expect the user to send (since we add it to our links or forms), and validate this nonce using a user token.

The user token should be uniquely generated and stored, and therefore we can store it in a browser cookie and expect the user to maintain it for a given interval of time. We generate a random hash, store in the cookie, and use this hash to produce other hashes named nonces which can be put into our forms and URLs.

To validate these nonces then, it’s a simple process of reproducing these hashes again using the same CSRF token, and then compare the user-submitted nonce against the one we just created using the unique user CSRF token.

Now if a hacker patches a URL with query arguments and puts a random nonce or their own nonce (which we created for them), then the targeted user (say victim) will need the hacker’s own CSRF token as well in order for the operation to be successful, which should not happen at all, as the CSRF token cookie should be stored with httponly flag which means it cannot be accessed or manipulated from the front-end.

Using PHP nonce helper

This is a very simple open source library I took couple hours to write and document, it is hosted over Github, it makes it easy for you to produce anti-CSRF nonce and add them to your URLs and web forms, and validate these nonces to make sure everything’s right.

Installation

With composer:

To install this library and its dependencies when you are using composer, simply run this command:

composer require elhardoum/nonce-php

Without composer:

If you are not using composer, then you want to download the code from a latest release (currently 0.1) from Github releases page, and extract the code to your project directory.

Nonce library depends on this package: https://github.com/ircmaxell/RandomLib, which itself depends on another package called `ircmaxell/SecurityLib` as far as I know, so you’ll want to download those as well.

Loading the library into your project:

If you used composer, then simply require the autoload.php file:

require __DIR__ . '/vendor/autoload.php';

otherwise if you didn’t use composer, then you’ll have to load the PHP classes included in the `src/` directory, and the same goes for `ircmaxell/RandomLib` and `ircmaxell/SecurityLib` as well.

require __DIR__ . '/nonce-php/src/Nonce.php';
require __DIR__ . '/nonce-php/src/Cookie.php';
require __DIR__ . '/nonce-php/src/Config.php';
require __DIR__ . '/nonce-php/src/HashStore.php';

To import the classes then use this:

use Nonce\Nonce, Nonce\Config;

PHP nonce usage

First, it’s important to add a random salt

Config::$SALT = 'wL`i%aQh4e|0Pg`7Nr`v|8cx(wzH>4+B<7GHNO]|1wXQ8XETfx+/ZnSklrr&YK~W';

Tip: you can make use of WordPress’s own random salts generator tool.

This library can be configured properly in order to store the CSRF cookie correctly, and store the hashes as well.

Add the cookie path and cookie domain settings. You can set the path to ‘/’ if your application is running on the root of a domain or sub-domain or IP, but otherwise set the directory properly.

The cookie host (domain) should be supplied as well.

Config::$COOKIE_PATH = '/test/';
Config::$COOKIE_DOMAIN = 'example.com';

The hashes (nonces) by default are store over browser cookies, but you can configure Nonce to store them somewhere else.

If you keep it that way, storing hashes over cookies, then make sure you don’t run yourself into a 400 error due to a large number of cookies supplied each time to the request headers. We can make sure we don’t run into that by the following:

  1. Make it so the hashes stored in the cookie are very small, use `Config::$HASH_NAME_LENGTH` for this.
  2. Try not to store too many nonces in a large amount of time, you can either limit the amount of forms tokenised, or expire the nonce cookie in a short time interval (`Config::$NONCE_EXPIRE` 10 min by default).
  3. Toggle the cookie directory every time related to your forms, if you are sure form A is located at `/test/auth/` directory, then toggle `Config::$COOKIE_PATH` to `/test/auth/` and restore it after. In other words, don’t store too many cookies on the global cookie space (root of your app).

Alright, given that we configured the most important part, let’s dive into the usage.

Create a new nonce:

To create a nonce for the user, we can make use of the `Nonce::create` method:

Nonce::create( string $action, int $expire = null )

The action should be something unique, because it plays an important role in generating the nonce hash, we can set it to `login-user` if we’re tokenizing a loging form, or “purchase-product-{$product_id}” if on a product form or URL. now it’s clearly obvious that a nonce produced with an action `test-action` should not be the same as another action tag `test-action2`. That’s the beauty of it.

The second argument is the nonce expiration, it’s not so important but if you want a nonce to live at X seconds then specify it, otherwise it will use the default `Config::$NONCE_EXPIRE` setting.

We can then use this as an example in our form code:

<?php echo Nonce::create('login-form'); ?>
<form method="post">
    ....
    ....

    <input type="hidden" name="nonce" value="<?php echo Nonce::create('login-form'); ?>" />
</form>

for which the markup should look something like:

<form method="post">
    ....
    ....

    <input type="hidden" name="nonce" value="bc66c85313ec237a5ad6bf" />
</form>

So that’s our user nonce right there. It’s ready to be used and verified, next step.

The same thing can be supplied to URLs, here’s an example:

http://example.com/test/logout?nonce=<?php echo Nonce::create('login-form'); ?>

Verifying a nonce:

Verifying nonces is simple, you can use `Nonce::verify` method for this:

Nonce::verify( string $nonce, string $action )

So pass the nonce from the form (or URL) as the first argument, and the action tag used to create the nonce in the first place as a second argument.

In our previous example, we can then use:

if ( isset( $_POST['nonce'] ) && Nonce::verify( $_POST['nonce'], 'login-form' ) ) {
    # nonce is valid, do things
} else {
    # tell the user to try again and be gentle
}

You can instantly delete the hash from the hash store (cookies, cache, or whatever you configure it to use), but it’s not that important as the tokens have a given lifetime and should be auto-deleted upon expiration.

Using Redis as the hash store

Redis is an awesome in-memory cache driver, can be used to store the hashes and expire them properly.

You’ll want to use a PHP Redis client, best out there is Predis:

$redis_client = new Predis\Client();

Config::$STORE_CTX_SET = function($key, $value, $expire) use ($redis_client) {
    $redis_client->set( $key, $value );
    $redis_client->expire( $key, $expire );
};

Config::$STORE_CTX_GET = function($key) use ($redis_client) {
    return $redis_client->get( $key );
};

Config::$STORE_CTX_DELETE = function($key) use ($redis_client) {
    return $redis_client->del( $key );
};

Using the database as the hash store

This is not recommended at all as you’d end up bloating your database with hashes, but if you’ll be running cron jobs or some script to delete expired hashes from the database then it’ll be fine to go with this.

You can simply alter the `Config::$STORE_CTX_*` callbacks, and Nonce will pass the arguments to them to get, set and delete hashes from the database.

I have previously wrote a meta-php class which helps you use the WordPress functions (update|get|delete)_(option|user_meta|post_meta), and it can be used here to store the hashes, get and delete them.

The library can be downloaded from Github

Config::$STORE_CTX_SET = function($key, $value, $expire) {
    update_option( $key, $value );
    update_option( "{$key}_expire", time() + $expire );
};

Config::$STORE_CTX_GET = function($key) {
    return get_option( $key );
};

Config::$STORE_CTX_DELETE = function($key) {
    return delete_option( $key );
};

The keys expiration are stored in the meta database as `*_expire`, when you run the cleanup script you can then get all the keys with `*_expire` where the `meta_value` is greater than the current unix timestamp:

select `meta_key` from `mydb`.`meta` where `meta_key` like '%_expire' and `meta_value` > UNIX_TIMESTAMP(NOW())

Then iterate through the keys and delete the expiration and the hashes as well, so you’d be ending up deleting 2 rows per hash.

More documentation

This is a quick tutorial introducing this PHP Nonce tool, therefore not all the configuration and usage is covered, please see the project’s Github page for more information. Thank you!

Digital Ocean

Cheap Cloud SSD Hosting

Get a VPS now starting at $5/m, fast and perfect for WordPress and PHP applications

Sign Up with $10 Credit