Initial commit
This commit is contained in:
commit
3899d191a4
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/composer.lock
|
||||||
|
/vendor
|
||||||
|
/.phpunit*
|
94
README.md
Normal file
94
README.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# SimpleJwt
|
||||||
|
|
||||||
|
This is a simple library for generating (signing) and verifying JWT tokens. It
|
||||||
|
is by no means an advanced library. If you just need to sign and refresh tokens
|
||||||
|
for users of your site or intranet, this will work great. If you need all the
|
||||||
|
glorious features of the JWT spec you should look elsewhere.
|
||||||
|
|
||||||
|
* Only handles HMAC-SHA256.
|
||||||
|
* Only handles expiry ('exp') natively
|
||||||
|
* Doesn't use any X.509 stuff.
|
||||||
|
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
Use this to avoid having to rewrite the wheel when implementing authorization
|
||||||
|
internally within a system where OAuth may be overkill.
|
||||||
|
|
||||||
|
* Make good use of the expiry. JWTs aren't armored in any way, so make sure
|
||||||
|
they can't be used longer than they have to. (An hour is a good idea)
|
||||||
|
* Make sure you understand the security aspects of JWTs.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install using composer:
|
||||||
|
|
||||||
|
$ composer require noccylabs/simple-jwt:@dev
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You need a key for both generating and parsing tokens. Create a `JwtDerivedKey`
|
||||||
|
or a `JwtPlaintextKey` and pass it to the `JwtToken` constructor:
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\Key\{JwtDerivedKey,JwtPlaintextKey}
|
||||||
|
|
||||||
|
// Derive a key using secret and salt...
|
||||||
|
$key = new JwtDerivedKey("secret", "salt");
|
||||||
|
// ...or use a prepared plaintext key
|
||||||
|
$key = new JwtPlaintextKey("This Should Be Binary Data..");
|
||||||
|
|
||||||
|
### Generating tokens
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\JwtToken;
|
||||||
|
|
||||||
|
$tok = new JwtToken($key);
|
||||||
|
$tok->setExpiry("1h");
|
||||||
|
$tok->claims->add("some/claim/MaxItems", 8);
|
||||||
|
|
||||||
|
$str = $tok->getSignedToken();
|
||||||
|
|
||||||
|
|
||||||
|
### Parsing tokens
|
||||||
|
|
||||||
|
Parsing is done by passing the raw token as the 2nd parameter
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\JwtToken;
|
||||||
|
|
||||||
|
$str = "...received token...";
|
||||||
|
|
||||||
|
$tok = new JwtToken($key, $str);
|
||||||
|
|
||||||
|
if (!$tok->isValid()) {
|
||||||
|
// This check works, but using the validator might be better
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using ->has() follwed by ->get() is one way
|
||||||
|
if ($tok->claims->has("some/claim/MaxItems")) {
|
||||||
|
// The claim exists, we can get the value (if any)
|
||||||
|
$val = $tok->claims->get("some/claim/MaxItems");
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can also use valueOf() to return a default value if needed
|
||||||
|
$val = $tok->claims->valueOf("some/claim/MaxItems", 64);
|
||||||
|
|
||||||
|
### Validating tokens
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\Validator\JwtValidator;
|
||||||
|
|
||||||
|
$validator = new JwtValidator();
|
||||||
|
// Require that the claim exists
|
||||||
|
$validator->addRequiredClaim("some/required/Claim");
|
||||||
|
// Require that the claim exists and has a value of true
|
||||||
|
$validator->addRequiredClaimWithValue("some/required/OtherClaim", true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Pass a JwtToken to validateToken()...
|
||||||
|
$valid = $validator->validateToken($tok);
|
||||||
|
// ...or pass a JwtKeyInterface and the raw string to validate()
|
||||||
|
$valid = $validator->validate($key, $tokenstr);
|
||||||
|
}
|
||||||
|
catch (JwtValidatorException $e) {
|
||||||
|
// validation failed
|
||||||
|
}
|
17
composer.json
Normal file
17
composer.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "noccylabs/simple-jwt",
|
||||||
|
"description": "Simple library for generating and verifying JWT tokens",
|
||||||
|
"type": "library",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christopher Vagnetoft",
|
||||||
|
"email": "cvagnetoft@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"NoccyLabs\\SimpleJwt\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
phpunit.xml
Normal file
22
phpunit.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
executionOrder="depends,defects"
|
||||||
|
forceCoversAnnotation="true"
|
||||||
|
beStrictAboutCoversAnnotation="true"
|
||||||
|
beStrictAboutOutputDuringTests="true"
|
||||||
|
beStrictAboutTodoAnnotatedTests="true"
|
||||||
|
verbose="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="default">
|
||||||
|
<directory suffix="Test.php">tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||||
|
<directory suffix=".php">src</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
125
src/Collection/PropertyBag.php
Normal file
125
src/Collection/PropertyBag.php
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Collection;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Countable;
|
||||||
|
|
||||||
|
class PropertyBag
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $props = [];
|
||||||
|
|
||||||
|
public function __construct(array $props=[])
|
||||||
|
{
|
||||||
|
$this->props = $props;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a property value, fails if the property exists
|
||||||
|
*
|
||||||
|
* @param string Property name
|
||||||
|
* @param mixed Value
|
||||||
|
* @throws PropertyException if the property already exists
|
||||||
|
*/
|
||||||
|
public function add(string $prop, $value)
|
||||||
|
{
|
||||||
|
if (array_key_exists($prop, $this->props)) {
|
||||||
|
throw new PropertyException("Property already exists");
|
||||||
|
}
|
||||||
|
$this->props[$prop] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a property value, create the property if it doesn't
|
||||||
|
* exist.
|
||||||
|
*
|
||||||
|
* @param string Property name
|
||||||
|
* @param mixed Value
|
||||||
|
*/
|
||||||
|
public function set(string $prop, $value)
|
||||||
|
{
|
||||||
|
$this->props[$prop] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of a property, fails if the property does not exist.
|
||||||
|
* Use the value() method to get with a default value
|
||||||
|
*
|
||||||
|
* @param string Property name
|
||||||
|
* @return mixed
|
||||||
|
* @throws PropertyException if the property does not exist
|
||||||
|
*/
|
||||||
|
public function get(string $prop)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($prop, $this->props)) {
|
||||||
|
throw new PropertyException("No such property");
|
||||||
|
}
|
||||||
|
return $this->props[$prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all properties.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
return $this->props;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all properties as a json string
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getJson(): string
|
||||||
|
{
|
||||||
|
return json_encode($this->props, JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the property, or use the provided default value.
|
||||||
|
*
|
||||||
|
* @param string Property name
|
||||||
|
* @param mixed Default value
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function value(string $prop, $default=null)
|
||||||
|
{
|
||||||
|
return array_key_exists($prop, $this->props)
|
||||||
|
? $this->props[$prop]
|
||||||
|
: $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a property
|
||||||
|
*/
|
||||||
|
public function delete(string $prop)
|
||||||
|
{
|
||||||
|
unset($this->props[$prop]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a property is present
|
||||||
|
*/
|
||||||
|
public function has(string $prop): bool
|
||||||
|
{
|
||||||
|
return array_key_exists($prop, $this->props);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if all the provided properties are present
|
||||||
|
*/
|
||||||
|
public function hasAll(array $props)
|
||||||
|
{
|
||||||
|
foreach ($props as $prop) {
|
||||||
|
if (!$this->has($prop)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
src/Collection/PropertyException.php
Normal file
9
src/Collection/PropertyException.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Collection;
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyException extends \RuntimeException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
146
src/JwtToken.php
Normal file
146
src/JwtToken.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt;
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\Collection\PropertyBag;
|
||||||
|
use NoccyLabs\SimpleJwt\Key\KeyInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @property-read header PropertyBag
|
||||||
|
* @property-read claim PropertyBag
|
||||||
|
*/
|
||||||
|
class JwtToken
|
||||||
|
{
|
||||||
|
/** @var PropertyBag */
|
||||||
|
private $header;
|
||||||
|
/** @var PropertyBag */
|
||||||
|
private $claims;
|
||||||
|
/** @var KeyInterface */
|
||||||
|
private $key;
|
||||||
|
/** @var bool */
|
||||||
|
private $valid;
|
||||||
|
/** @var bool */
|
||||||
|
private $generated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param KeyInterface The key used to sign the token
|
||||||
|
*/
|
||||||
|
public function __construct(KeyInterface $key, ?string $token=null)
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
|
||||||
|
if ($token) {
|
||||||
|
$this->parseToken($token);
|
||||||
|
} else {
|
||||||
|
$this->header = new PropertyBag([
|
||||||
|
'alg' => "HS256",
|
||||||
|
'typ' => "JWT",
|
||||||
|
]);
|
||||||
|
$this->claims = new PropertyBag();
|
||||||
|
$this->valid = true;
|
||||||
|
$this->generated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseToken(string $token)
|
||||||
|
{
|
||||||
|
$this->generated = false;
|
||||||
|
|
||||||
|
[ $header, $payload, $signature ] = explode(".", trim($token), 3);
|
||||||
|
$hash = JwtUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
|
||||||
|
if ($signature == $hash) {
|
||||||
|
$this->valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->header = new PropertyBag(json_decode(JwtUtil::decode($header), true));
|
||||||
|
$this->claims = new PropertyBag(json_decode(JwtUtil::decode($payload), true));
|
||||||
|
|
||||||
|
if ($this->header->has('exp')) {
|
||||||
|
$exp = intval($this->header->get('exp'));
|
||||||
|
if ($exp <= time()) {
|
||||||
|
// Invalid if expired
|
||||||
|
$this->valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValid(): bool
|
||||||
|
{
|
||||||
|
return $this->valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isGenerated(): bool
|
||||||
|
{
|
||||||
|
return $this->generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $key)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'header': return $this->header;
|
||||||
|
case 'claims': return $this->claims;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addClaim(string $name, $value)
|
||||||
|
{
|
||||||
|
$this->claims->add($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setClaim(string $name, $value)
|
||||||
|
{
|
||||||
|
$this->claims->set($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExpiry($expiry)
|
||||||
|
{
|
||||||
|
if ($expiry instanceof \DateTime) {
|
||||||
|
$this->header->set('exp', $expiry->format("U"));
|
||||||
|
} elseif ($expiry === null) {
|
||||||
|
$this->header->delete('exp');
|
||||||
|
} elseif (is_numeric($expiry)) {
|
||||||
|
if ($expiry < time()) {
|
||||||
|
$this->header->set('exp', time() + $expiry);
|
||||||
|
} else {
|
||||||
|
$this->header->set('exp', intval($expiry));
|
||||||
|
}
|
||||||
|
} elseif ($t = strtotime($expiry)) {
|
||||||
|
$this->header->set('exp', $t);
|
||||||
|
} elseif (preg_match('/([0-9]+)([wdhm])/', $expiry, $match)) {
|
||||||
|
switch ($match[2]) {
|
||||||
|
case 'w':
|
||||||
|
$fact = 60 * 60 * 24 * 7;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
$fact = 60 * 60 * 24;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
$fact = 60 * 60;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
$fact = 60;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$this->header->set('exp', time() + (intval($match[1]) * $fact));
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignedToken(): string
|
||||||
|
{
|
||||||
|
$header = JwtUtil::encode($this->header->getJson());
|
||||||
|
$payload = JwtUtil::encode($this->claims->getJson());
|
||||||
|
$hash = JwtUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
|
||||||
|
return $header.".".$payload.".".$hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
16
src/JwtUtil.php
Normal file
16
src/JwtUtil.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt;
|
||||||
|
|
||||||
|
|
||||||
|
class JwtUtil
|
||||||
|
{
|
||||||
|
public static function encode($data) {
|
||||||
|
return rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($data)), "=");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function decode($data) {
|
||||||
|
return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
src/Key/JwtDerivedKey.php
Normal file
19
src/Key/JwtDerivedKey.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Key;
|
||||||
|
|
||||||
|
class JwtDerivedKey implements KeyInterface
|
||||||
|
{
|
||||||
|
private $key;
|
||||||
|
|
||||||
|
public function __construct(string $password, string $salt, int $iter=100)
|
||||||
|
{
|
||||||
|
$this->key = hash_pbkdf2("sha256", $password, $salt, $iter, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBinaryKey(): string
|
||||||
|
{
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
19
src/Key/JwtPlaintextKey.php
Normal file
19
src/Key/JwtPlaintextKey.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Key;
|
||||||
|
|
||||||
|
class JwtPlaintextKey implements KeyInterface
|
||||||
|
{
|
||||||
|
private $key;
|
||||||
|
|
||||||
|
public function __construct(string $key)
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBinaryKey(): string
|
||||||
|
{
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
9
src/Key/KeyInterface.php
Normal file
9
src/Key/KeyInterface.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Key;
|
||||||
|
|
||||||
|
interface KeyInterface
|
||||||
|
{
|
||||||
|
public function getBinaryKey(): string;
|
||||||
|
}
|
||||||
|
|
9
src/Validator/JwtClaimException.php
Normal file
9
src/Validator/JwtClaimException.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Validator;
|
||||||
|
|
||||||
|
|
||||||
|
class JwtClaimException extends JwtValidatorException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
8
src/Validator/JwtHeaderException.php
Normal file
8
src/Validator/JwtHeaderException.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Validator;
|
||||||
|
|
||||||
|
class JwtHeaderException extends JwtValidatorException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
8
src/Validator/JwtTokenException.php
Normal file
8
src/Validator/JwtTokenException.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Validator;
|
||||||
|
|
||||||
|
class JwtTokenException extends JwtValidatorException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
57
src/Validator/JwtValidator.php
Normal file
57
src/Validator/JwtValidator.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Validator;
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\JwtToken;
|
||||||
|
use NoccyLabs\SimpleJwt\Key\KeyInterface;
|
||||||
|
|
||||||
|
class JwtValidator
|
||||||
|
{
|
||||||
|
private $requireHeaders = [];
|
||||||
|
|
||||||
|
private $requireClaims = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->requireHeaders = [
|
||||||
|
'alg',
|
||||||
|
'typ',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRequiredClaim(string $name)
|
||||||
|
{
|
||||||
|
$this->requireClaims[$name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRequiredClaimWithValue(string $name, $value)
|
||||||
|
{
|
||||||
|
$this->requireClaims[$name] = [ $value ];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateToken(JwtToken $token)
|
||||||
|
{
|
||||||
|
if (!$token->isValid()) {
|
||||||
|
throw new JwtTokenException("The token is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$token->header->hasAll($this->requireHeaders)) {
|
||||||
|
throw new JwtHeaderException("The token is missing one or more required headers");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$token->claims->hasAll($this->requireClaims)) {
|
||||||
|
throw new JwtHeaderException("The token is missing one or more required claims");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate(KeyInterface $key, string $raw)
|
||||||
|
{
|
||||||
|
$token = new JwtToken($key, $raw);
|
||||||
|
if ($this->validateToken($token)) {
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
8
src/Validator/JwtValidatorException.php
Normal file
8
src/Validator/JwtValidatorException.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Validator;
|
||||||
|
|
||||||
|
class JwtValidatorException extends \RuntimeException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
38
tests/JwtTokenTest.php
Normal file
38
tests/JwtTokenTest.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt;
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\Key\JwtPlaintextKey;
|
||||||
|
|
||||||
|
class JwtTokenTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testGeneratingTokens()
|
||||||
|
{
|
||||||
|
$key = new JwtPlaintextKey("test");
|
||||||
|
|
||||||
|
$tok = new JwtToken($key);
|
||||||
|
$tok->addClaim("foo", true);
|
||||||
|
|
||||||
|
$token = $tok->getSignedToken();
|
||||||
|
|
||||||
|
$this->assertNotNull($token);
|
||||||
|
$this->assertTrue($tok->isGenerated());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParsingTokens()
|
||||||
|
{
|
||||||
|
$key = new JwtPlaintextKey("test");
|
||||||
|
|
||||||
|
$tok = new JwtToken($key);
|
||||||
|
$tok->addClaim("foo", true);
|
||||||
|
|
||||||
|
$token = $tok->getSignedToken();
|
||||||
|
|
||||||
|
$parsed = new JwtToken($key, $token);
|
||||||
|
|
||||||
|
$this->assertTrue($parsed->isValid());
|
||||||
|
$this->assertFalse($parsed->isGenerated());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
18
tests/JwtUtilTest.php
Normal file
18
tests/JwtUtilTest.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt;
|
||||||
|
|
||||||
|
|
||||||
|
class JwtUtilTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testTheEncodingShouldBeSymmetric()
|
||||||
|
{
|
||||||
|
$v1a = "HelloWorld";
|
||||||
|
$v1b = JwtUtil::encode($v1a);
|
||||||
|
$v1c = JwtUtil::decode($v1b);
|
||||||
|
|
||||||
|
$this->assertEquals($v1a, $v1c);
|
||||||
|
$this->assertNotEquals($v1a, $v1b);
|
||||||
|
}
|
||||||
|
}
|
37
tests/Key/JwtDerivedKeyTest.php
Normal file
37
tests/Key/JwtDerivedKeyTest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Key;
|
||||||
|
|
||||||
|
|
||||||
|
class JwtDerivedKeyTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
public function testTheDerivedKeysShouldBeConsistent()
|
||||||
|
{
|
||||||
|
$key1a = new JwtDerivedKey("foo", "foosalt");
|
||||||
|
$key1b = new JwtDerivedKey("foo", "foosalt");
|
||||||
|
$this->assertNotNull($key1a);
|
||||||
|
$this->assertEquals($key1a->getBinaryKey(), $key1b->getBinaryKey());
|
||||||
|
|
||||||
|
$key2a = new JwtDerivedKey("bar", "foosalt");
|
||||||
|
$key2b = new JwtDerivedKey("bar", "barsalt");
|
||||||
|
$key2c = new JwtDerivedKey("bar", "barsalt");
|
||||||
|
$this->assertNotNull($key2a);
|
||||||
|
$this->assertNotEquals($key2a->getBinaryKey(), $key2b->getBinaryKey());
|
||||||
|
$this->assertEquals($key2b->getBinaryKey(), $key2c->getBinaryKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheDerivedKeysShouldBeUnique()
|
||||||
|
{
|
||||||
|
$keys = [];
|
||||||
|
$keys[] = (new JwtDerivedKey("foo", "foosalt"))->getBinaryKey();
|
||||||
|
$keys[] = (new JwtDerivedKey("foo", "barsalt"))->getBinaryKey();
|
||||||
|
$keys[] = (new JwtDerivedKey("foo", "bazsalt"))->getBinaryKey();
|
||||||
|
$keys[] = (new JwtDerivedKey("bar", "foosalt"))->getBinaryKey();
|
||||||
|
$keys[] = (new JwtDerivedKey("bar", "barsalt"))->getBinaryKey();
|
||||||
|
$keys[] = (new JwtDerivedKey("bar", "bazsalt"))->getBinaryKey();
|
||||||
|
|
||||||
|
$unique = array_unique($keys);
|
||||||
|
$this->assertEquals(count($keys), count($unique));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
tests/Key/JwtPlaintextKeyTest.php
Normal file
16
tests/Key/JwtPlaintextKeyTest.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Key;
|
||||||
|
|
||||||
|
|
||||||
|
class JwtPlaintextKeyTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
public function testThePlaintextKeyShouldBeReturned()
|
||||||
|
{
|
||||||
|
$key = new JwtPlaintextKey("foo");
|
||||||
|
$this->assertEquals("foo", $key->getBinaryKey());
|
||||||
|
|
||||||
|
$key = new JwtPlaintextKey("bar");
|
||||||
|
$this->assertEquals("bar", $key->getBinaryKey());
|
||||||
|
}
|
||||||
|
}
|
34
tests/Validator/JwtValidatorTest.php
Normal file
34
tests/Validator/JwtValidatorTest.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJwt\Validator;
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJwt\JwtToken;
|
||||||
|
use NoccyLabs\SimpleJwt\Key\JwtPlaintextKey;
|
||||||
|
|
||||||
|
class JwtValidatorTest extends \PhpUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testValidKeysShouldPassWithDefaultConfiguration()
|
||||||
|
{
|
||||||
|
$key = new JwtPlaintextKey("key");
|
||||||
|
$token = new JwtToken($key);
|
||||||
|
|
||||||
|
$validator = new JwtValidator();
|
||||||
|
$valid = $validator->validateToken($token);
|
||||||
|
$this->assertEquals(true, $valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExpiredKeysShouldFailWithException()
|
||||||
|
{
|
||||||
|
$key = new JwtPlaintextKey("key");
|
||||||
|
$token = new JwtToken($key);
|
||||||
|
$token->header->set("exp", 0);
|
||||||
|
|
||||||
|
$token = new JwtToken($key, $token->getSignedToken());
|
||||||
|
|
||||||
|
$validator = new JwtValidator();
|
||||||
|
$this->expectException(JwtTokenException::class);
|
||||||
|
$valid = $validator->validateToken($token);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user