Original creation date: 23th September 2019
Note: Originally I posted this on my Wordpress Blog (https://1337codersblog.wordpress.com), but I decided to switch to steemit, so I copied it from there.
How to implement WordPress Nonces in PHP
Last month I applied for a Job as a working student at Inpsyde. In the process of the application I had to solve a demo task to show my skills.
The task was to implement the wp_nonce functions in an object oriented way, that provides the core functionalities of WordPress but better maintainable and checkable. Also I had to provide Unit Tests, use Composer, interfaces, Autoloading and PHP 7.0. This is how I solved it:
First I defined an interface containing all wp_nonce functions and added documentation to them for better understanding:
<?php namespace Demo\Classes;
use Spatie;
use DateTime;
Interface Nonce {
/**
* Appends the Nonce hash to a given URL
* @param string $url Url where to append the Nonce to
* @return Spatie\Url\Url The Url with the Nonce appended
*/
public function wp_nonce_url(Spatie\Url\Url $url): Spatie\Url\Url;
/**
* Generates fields with Nonce for form validation
* @param string $referer If this is not null or empty a hidden field is created with the content of this parameter. The content should be the result of a wp_referer_field call.
* @return string The HTML code with the fields or null if $echo is true.
*/
public function wp_nonce_field(string $referer=""): string;
/**
* Checks whether this Nonce is valid or not.
* @return bool True, if the Nonce is valid. False else.
*/
public function wp_verify_nonce(): bool;
public function get_expiration_date(): DateTime;
public function get_name(): string;
public function get_hash(): string;
public function get_action(): string;
}
Then I began implementing the interface. WordPress Nonces are objects that are unique and valid until a specific date and time. They are used to prevent Cross-Site-Request-Forgery. To meet this requirement I decided to combine date, time and an incrementing ID and calculate a hash of it. I choose this way of implementation because the date and time of the current server has limited resolution. If multiple Nonces are created between to ticks of the server’s clock, there would be two Nonces which are equal which would violate the requirement that each Nonce must be unique. So additionaly an ID number, which is incremented each time a Nonce is created, is added before calculating the hash. This way the Nonce also is unique if the ID number, which is of integer type, overflows, because in that moment when that happens the date and time will be different from the time when the ID number was in its initial state, so the calculated hash still is unique. The generated hash identifies the Nonce. Also at the time the Nonce is created, the expiration date is set:
function __construct(string $action="", string $name="_wpnonce", DateInterval $lifeTime=null){
$dateTime = new DateTime();
$this->hash = hash("sha256", CountingDateTimeNonce::$nextId."".$dateTime->format("Y-m-d H:i:s"));
CountingDateTimeNonce::$nextId++;
$this->action = $action;
$this->name = $name;
if (is_null($lifeTime)){
$this->expirationDate = $dateTime->add(new DateInterval("P1D"));
assert(!is_null($this->expirationDate));
}
else{
$this->expirationDate = $dateTime->add($lifeTime);
assert(!is_null($this->expirationDate));
}
}
Next I added all dependencies via Composer:
composer require –dev phpunit/phpunit 8.3
composer require spatie/url 1.3
I also enabled Autoload by adding following lines to the top of composer.json:
"autoload": {
"psr-4": {
"Demo\\" : "."
}
},
Then I implemented the wp_nonce_field, wp_nonce_url and wp_verify_nonce functions:
public function wp_nonce_field(string $referer=""): string {
$result = '<input type="hidden" id="save_cpt_wpnonce" value="'.$this->hash.'">';
if (!empty($referer)){
$result = $result.'<input type="hidden" name="_wp_http_referer" value="'.$referer.'">';
}
return $result;
}
public function wp_nonce_url(Spatie\Url\Url $url): Spatie\Url\Url {
$result = $url;
if (!empty($this->action)){
$result = $result->withQueryParameter("action", $this->action);
}
$result = $result->withQueryParameter($this->name, $this->hash);
return $result;
}
public function wp_verify_nonce(): bool {
$currentDateTime = new DateTime();
return $currentDateTime < $this->expirationDate;
}
You can find the complete source code in my Bitbucket repo.