I haven't been here in a while; I miss this place. Hope everyone is doing well. Without further adieu...
Up until recently I have always used the session-based method to implement security tokens. Page loads, a token is generated and printed to something like a hidden input field. The token is also stored in the session. When the form is submitted, the token goes along with it and the passed token is compared to what's in the session. If it matches, great, if not, some kind of error.
However, this has issues if you want to have multiple tabs open with the application or do asynchronous calls. As soon as another tab is opened, the token stored in the session is going to be overwritten, invalidating the first tab. Likewise, with asynchronous calls, you basically have to wait in queue which doesn't make them asynchronous at all.
The application I have been working on for my client was really hoping to be able to have multiple tabs open for their clients. Plus it is just convenient. So I did some searching online on how people handle this. One of the things I came across was signed tokens. Basically you remove the session part altogether and simply just verify the signed token when the form is sent. The entire value is a combination of a hashed value (with a server-side secret) and the token.
Below is a class I created to handle this (some code has been lifted from Stackoverflow):
/**
* The Token class.
*/
class Token
{
/**
* The hashing algorithm used to hash the token data.
*
* @access public
* @var string
*/
const ALGORITHM = 'sha256';
/**
* Generates a cryptographically signed token.
*
* @return string Returns the new token.
*/
public static function generate() : string
{
$token = bin2hex(random_bytes(32));
$hash = hash_hmac(self::ALGORITHM, $token, TOKEN_SECRET);
$signed = $token.'-'.$hash;
return $signed;
}
/**
* Verifies a signed token.
*
* @param string $signedToken The signed token to verify.
*
* @throws InvalidTokenException
* @throws TokenMismatchException
*
* @return void
*/
public static function verify(string $signedToken)
{
$parts = explode('-', $signedToken);
if (count($parts) !== 2) {
throw new InvalidTokenException('Invalid token form!');
}
list($token, $hash) = $parts;
if ($hash !== hash_hmac(self::ALGORITHM, $token, TOKEN_SECRET)) {
throw new TokenMismatchException('Token could not be verified!');
}
}
}
The TOKEN_SECRET can be whatever you want, obviously stored outside the web root in a secure place. You could add some additional security by adding a timestamp to the token to allow expirations, or the user's ID so the tokens generated during their use of the application would be exclusive to them.
I much prefer this method now as it's much simpler to implement and maintain than what I was doing previously. Also just feels much more elegant.
Any feedback would be greatly appreciated. Would also be interested in hearing your methods of implementing security tokens.
Thanks!