Skip to content

Instantly share code, notes, and snippets.

@nilesolutions
Created September 15, 2017 09:38
Show Gist options
  • Save nilesolutions/43513320483f990d3fea89a57b9869df to your computer and use it in GitHub Desktop.
Save nilesolutions/43513320483f990d3fea89a57b9869df to your computer and use it in GitHub Desktop.
<?php
/**
* Created by PhpStorm.
* User: thiphariel
* Date: 15/08/17
* Time: 19:40
*/
namespace App\Api;
use Exception;
class Token
{
/**
* @var array
*/
protected static $supportedAlgs = [
'HS256' => ['hash_hmac', 'SHA256'],
'HS512' => ['hash_hmac', 'SHA512'],
'HS384' => ['hash_hmac', 'SHA384'],
];
/**
* Generate a JWT
* @param array $payload
* @param string $key
* @param string $alg
* @return string
* @throws Exception
*/
public static function encode(array $payload, string $key, string $alg = 'HS256'): string
{
$header = [
'typ' => 'JWT',
'alg' => $alg
];
$segments = [];
$segments[] = static::base64($header);
$segments[] = static::base64($payload);
$msg = implode('.', $segments);
// Creating the signature, a hash with the s256 algorithm and the secret key. The signature is also base64 encoded.
$signature = static::sign($msg, $key, $alg);
if (empty($signature)) {
throw new Exception('Signature null');
}
$segments[] = static::base64($signature);
return implode('.', $segments);
}
public static function decode(string $token, string $key, array $allowedAlgs = []): string
{
// Split a string by '.'
$segments = explode('.', $token);
// Throw an exception if we don't have 3 segments
if (count($segments) != 3) {
throw new Exception('Wrong number of segments');
}
list($headb64, $payloadb64, $signb64) = $segments;
$header = json_decode(base64_decode($headb64));
if (empty($header->alg)) {
throw new Exception('Empty algorithm');
}
if (empty(static::$supportedAlgs[$header->alg])) {
throw new Exception('Algorithm not supported');
}
if (!in_array($header->alg, $allowedAlgs)) {
throw new Exception('Algorithm not allowed');
}
// Check the signature
if (!static::verify("$headb64.$payloadb64", base64_decode($signb64), $key, $header->alg)) {
throw new Exception('Signature verification failed');
}
$payload = base64_decode($payloadb64);
// Check if this token has expired.
$expired = json_decode($payload)->exp;
if (isset($expired) && time() >= $expired) {
throw new Exception('Expired token');
}
return $payload;
}
/**
* Creating the signature, a hash with the s256 algorithm and the secret key.
* The signature is also base64 encoded.
* @param string $msg
* @param string $key
* @param string $alg
* @return string|null
* @throws Exception
*/
private static function sign(string $msg, string $key, string $alg = 'HS256'): ?string
{
if (empty(static::$supportedAlgs[$alg])) {
throw new Exception('Algorithm not supported');
}
list($function, $algorithm) = static::$supportedAlgs[$alg];
switch ($function) {
case 'hash_hmac':
return hash_hmac($algorithm, $msg, $key, true);
}
return null;
}
/**
* @param string $msg
* @param string $signature
* @param string $key
* @param string $alg
* @return string
* @throws Exception
*/
private static function verify(string $msg, string $signature, string $key, string $alg): string
{
if (empty(static::$supportedAlgs[$alg])) {
throw new Exception('Algorithm not supported');
}
list($function, $algorithm) = static::$supportedAlgs[$alg];
switch ($function) {
case 'hash_hmac':
default:
$hash = hash_hmac($algorithm, $msg, $key, true);
return hash_equals($signature, $hash);
}
}
/**
* @param array|string $msg
* @return string
*/
private static function base64($msg): string
{
if (is_array($msg)) {
$msg = json_encode($msg);
}
return base64_encode($msg);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment