Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
f83998e6c7 | |||
369514589f | |||
b0566e3148 | |||
c5dd773026 | |||
5c422226fd | |||
e30ded1e66 | |||
822b796d40 | |||
b9c690cb6e | |||
fabf160346 | |||
953e831d84 | |||
5a4a2845e4 |
7
.woodpecker.yml
Normal file
7
.woodpecker.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pipeline:
|
||||||
|
phpunit:
|
||||||
|
image: walkero/phpunit-alpine:php8.1-phpunit9
|
||||||
|
commands:
|
||||||
|
- composer install
|
||||||
|
- vendor/bin/phpunit --testdox --no-progress
|
||||||
|
- vendor/bin/phpstan --no-progress
|
14
CHANGELOG.md
Normal file
14
CHANGELOG.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# SimpleJWT ChangeLog
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
- Mostly code cleanup
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
- Class- and filenames have had their case changed (`Jwt`→`JWT`)
|
||||||
|
- Added phpstan for static analysis
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
- Initial release
|
96
README.md
96
README.md
@ -1,4 +1,4 @@
|
|||||||
# SimpleJwt
|
# SimpleJWT
|
||||||
|
|
||||||
This is a simple library for generating (signing) and verifying JWT tokens. It
|
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
|
is by no means an advanced library. If you just need to sign and refresh tokens
|
||||||
@ -27,69 +27,77 @@ Install using composer:
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
You need a key for both generating and parsing tokens. Create a `JwtDerivedKey`
|
You need a key for both generating and parsing tokens. Create a `JWTDerivedKey`
|
||||||
or a `JwtPlaintextKey` and pass it to the `JwtToken` constructor:
|
or a `JWTPlaintextKey` and pass it to the `JWTToken` constructor:
|
||||||
|
|
||||||
use NoccyLabs\SimpleJwt\Key\{JwtDerivedKey,JwtPlaintextKey}
|
```php
|
||||||
|
use NoccyLabs\SimpleJWT\Key\{JWTDerivedKey,JWTPlaintextKey}
|
||||||
|
|
||||||
// Derive a key using secret and salt...
|
// Derive a key using secret and salt...
|
||||||
$key = new JwtDerivedKey("secret", "salt");
|
$key = new JWTDerivedKey("secret", "salt");
|
||||||
// ...or use a prepared plaintext key
|
// ...or use a prepared plaintext key
|
||||||
$key = new JwtPlaintextKey("This Should Be Binary Data..");
|
$key = new JWTPlaintextKey("This Should Be Binary Data..");
|
||||||
|
```
|
||||||
|
|
||||||
|
`JWTDerivedKey` uses hash_pbkdf2.
|
||||||
|
|
||||||
### Generating tokens
|
### Generating tokens
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
use NoccyLabs\SimpleJWT\JWTToken;
|
||||||
|
|
||||||
use NoccyLabs\SimpleJwt\JwtToken;
|
$tok = new JWTToken($key);
|
||||||
|
$tok->setExpiry("1h");
|
||||||
$tok = new JwtToken($key);
|
$tok->claims->add("some/claim/MaxItems", 8);
|
||||||
$tok->setExpiry("1h");
|
|
||||||
$tok->claims->add("some/claim/MaxItems", 8);
|
|
||||||
|
|
||||||
$str = $tok->getSignedToken();
|
|
||||||
|
|
||||||
|
$str = $tok->getSignedToken();
|
||||||
|
```
|
||||||
|
|
||||||
### Parsing tokens
|
### Parsing tokens
|
||||||
|
|
||||||
Parsing is done by passing the raw token as the 2nd parameter
|
Parsing is done by passing the raw token as the 2nd parameter
|
||||||
|
|
||||||
use NoccyLabs\SimpleJwt\JwtToken;
|
```php
|
||||||
|
use NoccyLabs\SimpleJWT\JWTToken;
|
||||||
|
|
||||||
$str = "...received token...";
|
$str = "...received token...";
|
||||||
|
|
||||||
$tok = new JwtToken($key, $str);
|
$tok = new JWTToken($key, $str);
|
||||||
|
|
||||||
if (!$tok->isValid()) {
|
if (!$tok->isValid()) {
|
||||||
// This check works, but using the validator might be better
|
// This check works, but using the validator might be better
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using ->has() follwed by ->get() is one way
|
// Using ->has() follwed by ->get() is one way
|
||||||
if ($tok->claims->has("some/claim/MaxItems")) {
|
if ($tok->claims->has("some/claim/MaxItems")) {
|
||||||
// The claim exists, we can get the value (if any)
|
// The claim exists, we can get the value (if any)
|
||||||
$val = $tok->claims->get("some/claim/MaxItems");
|
$val = $tok->claims->get("some/claim/MaxItems");
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can also use valueOf() to return a default value if needed
|
// You can also use valueOf() to return a default value if needed
|
||||||
$val = $tok->claims->valueOf("some/claim/MaxItems", 64);
|
$val = $tok->claims->valueOf("some/claim/MaxItems", 64);
|
||||||
|
```
|
||||||
|
|
||||||
### Validating tokens
|
### Validating tokens
|
||||||
|
|
||||||
use NoccyLabs\SimpleJwt\Validator\JwtValidator;
|
```php
|
||||||
|
use NoccyLabs\SimpleJWT\Validator\JWTValidator;
|
||||||
|
|
||||||
$validator = new JwtValidator();
|
$validator = new JWTValidator();
|
||||||
// Require that some claim exists
|
// Require that some claim exists
|
||||||
$validator
|
$validator
|
||||||
->requireIssuer("api.issuer.tld")
|
->requireIssuer("api.issuer.tld")
|
||||||
->requireAudience(["api.issuer.tld", "foo.issuer.tld"])
|
->requireAudience(["api.issuer.tld", "foo.issuer.tld"])
|
||||||
->addRequiredClaim("some/required/Claim");
|
->addRequiredClaim("some/required/Claim");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pass a JwtToken to validateToken()...
|
// Pass a JWTToken to validateToken()...
|
||||||
$valid = $validator->validateToken($tok);
|
$valid = $validator->validateToken($tok);
|
||||||
// ...or pass a JwtKeyInterface and the raw string to validate()
|
// ...or pass a JWTKeyInterface and the raw string to validate()
|
||||||
$valid = $validator->validate($key, $tokenstr);
|
$valid = $validator->validate($key, $tokenstr);
|
||||||
}
|
}
|
||||||
catch (JwtValidatorException $e) {
|
catch (JWTValidatorException $e) {
|
||||||
// validation failed
|
// validation failed
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
@ -11,7 +11,15 @@
|
|||||||
],
|
],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"NoccyLabs\\SimpleJwt\\": "src/"
|
"NoccyLabs\\SimpleJWT\\": "src/"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4|^8.0",
|
||||||
|
"ext-json": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^10.0",
|
||||||
|
"phpstan/phpstan": "^1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
phpstan.neon
Normal file
12
phpstan.neon
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
|
||||||
|
excludePaths:
|
||||||
|
- doc
|
||||||
|
- vendor
|
||||||
|
- tests
|
||||||
|
|
||||||
|
# Paths to include in the analysis
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
|
31
phpunit.xml
31
phpunit.xml
@ -1,22 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" bootstrap="vendor/autoload.php" executionOrder="depends,defects" beStrictAboutOutputDuringTests="true" cacheDirectory=".phpunit.cache" requireCoverageMetadata="true" beStrictAboutCoverageMetadata="true">
|
||||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
<coverage>
|
||||||
bootstrap="vendor/autoload.php"
|
<include>
|
||||||
executionOrder="depends,defects"
|
<directory suffix=".php">src</directory>
|
||||||
forceCoversAnnotation="true"
|
</include>
|
||||||
beStrictAboutCoversAnnotation="true"
|
</coverage>
|
||||||
beStrictAboutOutputDuringTests="true"
|
<testsuites>
|
||||||
beStrictAboutTodoAnnotatedTests="true"
|
<testsuite name="default">
|
||||||
verbose="true">
|
<directory suffix="Test.php">tests</directory>
|
||||||
<testsuites>
|
</testsuite>
|
||||||
<testsuite name="default">
|
</testsuites>
|
||||||
<directory suffix="Test.php">tests</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
|
|
||||||
<filter>
|
|
||||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
|
||||||
<directory suffix=".php">src</directory>
|
|
||||||
</whitelist>
|
|
||||||
</filter>
|
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Collection;
|
namespace NoccyLabs\SimpleJWT\Collection;
|
||||||
|
|
||||||
use ArrayAccess;
|
use ArrayAccess;
|
||||||
use Countable;
|
use Countable;
|
||||||
@ -21,8 +21,8 @@ class PropertyBag
|
|||||||
/**
|
/**
|
||||||
* Add a property value, fails if the property exists
|
* Add a property value, fails if the property exists
|
||||||
*
|
*
|
||||||
* @param string Property name
|
* @param string $prop Property name
|
||||||
* @param mixed Value
|
* @param mixed $value Value
|
||||||
* @throws PropertyException if the property already exists
|
* @throws PropertyException if the property already exists
|
||||||
*/
|
*/
|
||||||
public function add(string $prop, $value)
|
public function add(string $prop, $value)
|
||||||
@ -37,14 +37,19 @@ class PropertyBag
|
|||||||
* Set a property value, create the property if it doesn't
|
* Set a property value, create the property if it doesn't
|
||||||
* exist.
|
* exist.
|
||||||
*
|
*
|
||||||
* @param string Property name
|
* @param string $prop Property name
|
||||||
* @param mixed Value
|
* @param mixed $value Value
|
||||||
*/
|
*/
|
||||||
public function set(string $prop, $value)
|
public function set(string $prop, $value)
|
||||||
{
|
{
|
||||||
$this->props[$prop] = $value;
|
$this->props[$prop] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply properties without removing anything.
|
||||||
|
*
|
||||||
|
* @param array $props The properties to apply
|
||||||
|
*/
|
||||||
public function setAll(array $props)
|
public function setAll(array $props)
|
||||||
{
|
{
|
||||||
$this->props = array_merge(
|
$this->props = array_merge(
|
||||||
@ -57,7 +62,7 @@ class PropertyBag
|
|||||||
* Get the value of a property, fails if the property does not exist.
|
* Get the value of a property, fails if the property does not exist.
|
||||||
* Use the value() method to get with a default value
|
* Use the value() method to get with a default value
|
||||||
*
|
*
|
||||||
* @param string Property name
|
* @param string $prop Property name
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws PropertyException if the property does not exist
|
* @throws PropertyException if the property does not exist
|
||||||
*/
|
*/
|
||||||
@ -92,8 +97,8 @@ class PropertyBag
|
|||||||
/**
|
/**
|
||||||
* Get the value of the property, or use the provided default value.
|
* Get the value of the property, or use the provided default value.
|
||||||
*
|
*
|
||||||
* @param string Property name
|
* @param string $prop Property name
|
||||||
* @param mixed Default value
|
* @param mixed|null $default Default value
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function valueOf(string $prop, $default=null)
|
public function valueOf(string $prop, $default=null)
|
||||||
@ -105,6 +110,8 @@ class PropertyBag
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a property
|
* Remove a property
|
||||||
|
*
|
||||||
|
* @param string $prop Property name
|
||||||
*/
|
*/
|
||||||
public function delete(string $prop)
|
public function delete(string $prop)
|
||||||
{
|
{
|
||||||
@ -113,6 +120,8 @@ class PropertyBag
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a property is present
|
* Check if a property is present
|
||||||
|
*
|
||||||
|
* @param string $prop Property name
|
||||||
*/
|
*/
|
||||||
public function has(string $prop): bool
|
public function has(string $prop): bool
|
||||||
{
|
{
|
||||||
@ -121,6 +130,8 @@ class PropertyBag
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if all the provided properties are present
|
* Check if all the provided properties are present
|
||||||
|
*
|
||||||
|
* @param array $props Property names
|
||||||
*/
|
*/
|
||||||
public function hasAll(array $props)
|
public function hasAll(array $props)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Collection;
|
namespace NoccyLabs\SimpleJWT\Collection;
|
||||||
|
|
||||||
|
|
||||||
class PropertyException extends \RuntimeException
|
class PropertyException extends \RuntimeException
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt;
|
namespace NoccyLabs\SimpleJWT;
|
||||||
|
|
||||||
use NoccyLabs\SimpleJwt\Collection\PropertyBag;
|
use NoccyLabs\SimpleJWT\Collection\PropertyBag;
|
||||||
use NoccyLabs\SimpleJwt\Key\KeyInterface;
|
use NoccyLabs\SimpleJWT\Key\KeyInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @property-read header PropertyBag
|
* @property-read PropertyBag $header
|
||||||
* @property-read claim PropertyBag
|
* @property-read PropertyBag $claims
|
||||||
*/
|
*/
|
||||||
class JwtToken
|
class JWTToken
|
||||||
{
|
{
|
||||||
/** @var PropertyBag */
|
/** @var PropertyBag */
|
||||||
private $header;
|
private $header;
|
||||||
@ -30,7 +30,8 @@ class JwtToken
|
|||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param KeyInterface The key used to sign the token
|
* @param KeyInterface $key The key used to sign the token
|
||||||
|
* @param string|null $token Token data
|
||||||
*/
|
*/
|
||||||
public function __construct(KeyInterface $key, ?string $token=null)
|
public function __construct(KeyInterface $key, ?string $token=null)
|
||||||
{
|
{
|
||||||
@ -54,13 +55,13 @@ class JwtToken
|
|||||||
$this->generated = false;
|
$this->generated = false;
|
||||||
|
|
||||||
[ $header, $payload, $signature ] = explode(".", trim($token), 3);
|
[ $header, $payload, $signature ] = explode(".", trim($token), 3);
|
||||||
$hash = JwtUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
|
$hash = JWTUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
|
||||||
if ($signature == $hash) {
|
if ($signature == $hash) {
|
||||||
$this->valid = true;
|
$this->valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->header = new PropertyBag(json_decode(JwtUtil::decode($header), true));
|
$this->header = new PropertyBag(json_decode(JWTUtil::decode($header), true));
|
||||||
$this->claims = new PropertyBag(json_decode(JwtUtil::decode($payload), true));
|
$this->claims = new PropertyBag(json_decode(JWTUtil::decode($payload), true));
|
||||||
|
|
||||||
if ($this->header->has('exp')) {
|
if ($this->header->has('exp')) {
|
||||||
$exp = intval($this->header->get('exp'));
|
$exp = intval($this->header->get('exp'));
|
||||||
@ -69,13 +70,35 @@ class JwtToken
|
|||||||
$this->valid = false;
|
$this->valid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->header->has('nbf')) {
|
||||||
|
$nbf = intval($this->header->get('nbf'));
|
||||||
|
if ($nbf >= time()) {
|
||||||
|
// Invalid if before
|
||||||
|
$this->valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the expiry is not in the past.
|
||||||
|
*
|
||||||
|
* NOTE: This function will return true if the expiry header is missing, and
|
||||||
|
* it will not validate any claims. For actual verification of a token matching
|
||||||
|
* issuers, audience or other claims, see Validator\JWTValidator.
|
||||||
|
*
|
||||||
|
* @return bool True if the token expiry timestamp is missing or in the future
|
||||||
|
*/
|
||||||
public function isValid(): bool
|
public function isValid(): bool
|
||||||
{
|
{
|
||||||
return $this->valid;
|
return $this->valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the token was generated as opposed to parsed.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
public function isGenerated(): bool
|
public function isGenerated(): bool
|
||||||
{
|
{
|
||||||
return $this->generated;
|
return $this->generated;
|
||||||
@ -89,16 +112,44 @@ class JwtToken
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a claim to a token. Throws an exception if the claim already exists.
|
||||||
|
*
|
||||||
|
* @param string $name The name of the claim
|
||||||
|
* @param mixed $value Claim value
|
||||||
|
* @throws \NoccyLabs\SimpleJWT\Collection\PropertyException if the claim already exists.
|
||||||
|
*/
|
||||||
public function addClaim(string $name, $value)
|
public function addClaim(string $name, $value)
|
||||||
{
|
{
|
||||||
$this->claims->add($name, $value);
|
$this->claims->add($name, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a claim to a token. If the claim already exists it will be updated with
|
||||||
|
* the provided value.
|
||||||
|
*
|
||||||
|
* @param string $name The name of the claim
|
||||||
|
* @param mixed $value Claim value
|
||||||
|
*/
|
||||||
public function setClaim(string $name, $value)
|
public function setClaim(string $name, $value)
|
||||||
{
|
{
|
||||||
$this->claims->set($name, $value);
|
$this->claims->set($name, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time of expiry for the token.
|
||||||
|
*
|
||||||
|
* The expiry can be supplied as:
|
||||||
|
* - \DateTime instance
|
||||||
|
* - Unixtime as an integer
|
||||||
|
* - A string represening the expiry time
|
||||||
|
* - A period followed by a letter (m,h,d,w)
|
||||||
|
* - null, to unset the expiry
|
||||||
|
*
|
||||||
|
* @param string|int|\DateTime $expiry
|
||||||
|
* @return void
|
||||||
|
* @throws \InvalidArgumentException if the argument can not be interpreted
|
||||||
|
*/
|
||||||
public function setExpiry($expiry)
|
public function setExpiry($expiry)
|
||||||
{
|
{
|
||||||
if ($expiry instanceof \DateTime) {
|
if ($expiry instanceof \DateTime) {
|
||||||
@ -127,6 +178,8 @@ class JwtToken
|
|||||||
case 'm':
|
case 'm':
|
||||||
$fact = 60;
|
$fact = 60;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw new \InvalidArgumentException();
|
||||||
}
|
}
|
||||||
$this->header->set('exp', time() + (intval($match[1]) * $fact));
|
$this->header->set('exp', time() + (intval($match[1]) * $fact));
|
||||||
} else {
|
} else {
|
||||||
@ -136,9 +189,9 @@ class JwtToken
|
|||||||
|
|
||||||
public function getSignedToken(): string
|
public function getSignedToken(): string
|
||||||
{
|
{
|
||||||
$header = JwtUtil::encode($this->header->getJson());
|
$header = JWTUtil::encode($this->header->getJson());
|
||||||
$payload = JwtUtil::encode($this->claims->getJson());
|
$payload = JWTUtil::encode($this->claims->getJson());
|
||||||
$hash = JwtUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
|
$hash = JWTUtil::encode(hash_hmac("sha256", $header.".".$payload, $this->key->getBinaryKey(), true));
|
||||||
return $header.".".$payload.".".$hash;
|
return $header.".".$payload.".".$hash;
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt;
|
namespace NoccyLabs\SimpleJWT;
|
||||||
|
|
||||||
|
|
||||||
class JwtUtil
|
class JWTUtil
|
||||||
{
|
{
|
||||||
public static function encode($data) {
|
public static function encode($data) {
|
||||||
return rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($data)), "=");
|
return rtrim(str_replace(['+', '/'], ['-', '_'], base64_encode($data)), "=");
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Key;
|
namespace NoccyLabs\SimpleJWT\Key;
|
||||||
|
|
||||||
class JwtDerivedKey implements KeyInterface
|
class JWTDerivedKey implements KeyInterface
|
||||||
{
|
{
|
||||||
private $key;
|
private $key;
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Key;
|
namespace NoccyLabs\SimpleJWT\Key;
|
||||||
|
|
||||||
class JwtPlaintextKey implements KeyInterface
|
class JWTPlaintextKey implements KeyInterface
|
||||||
{
|
{
|
||||||
private $key;
|
private $key;
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Key;
|
namespace NoccyLabs\SimpleJWT\Key;
|
||||||
|
|
||||||
interface KeyInterface
|
interface KeyInterface
|
||||||
{
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Validator;
|
namespace NoccyLabs\SimpleJWT\Validator;
|
||||||
|
|
||||||
use NoccyLabs\SimpleJwt\JwtToken;
|
use NoccyLabs\SimpleJWT\JWTToken;
|
||||||
use NoccyLabs\SimpleJwt\Key\KeyInterface;
|
use NoccyLabs\SimpleJWT\Key\KeyInterface;
|
||||||
|
|
||||||
class JwtValidator
|
class JWTValidator
|
||||||
{
|
{
|
||||||
private $requireHeaders = [];
|
private $requireHeaders = [];
|
||||||
|
|
||||||
@ -41,32 +41,44 @@ class JwtValidator
|
|||||||
$this->requireAudience = (array)$audience;
|
$this->requireAudience = (array)$audience;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateToken(JwtToken $token)
|
public function validateToken(JWTToken $token)
|
||||||
{
|
{
|
||||||
if (!$token->isValid()) {
|
if (!$token->isValid()) {
|
||||||
throw new JwtTokenException("The token is not valid");
|
throw new JWTTokenException("The token is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->claims->has("nbf")) {
|
||||||
|
$notBefore = intval($token->claims->get("nbf"));
|
||||||
|
if (time() < $notBefore)
|
||||||
|
throw new JWTTokenException("Token not yet valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token->claims->has("exp")) {
|
||||||
|
$notAfter = intval($token->claims->get("exp"));
|
||||||
|
if (time() > $notAfter)
|
||||||
|
throw new JWTTokenException("Token no longer valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$token->header->hasAll($this->requireHeaders)) {
|
if (!$token->header->hasAll($this->requireHeaders)) {
|
||||||
throw new JwtHeaderException("The token is missing one or more required headers");
|
throw new JWTHeaderException("The token is missing one or more required headers");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$token->claims->hasAll($this->requireClaims)) {
|
if (!$token->claims->hasAll($this->requireClaims)) {
|
||||||
throw new JwtHeaderException("The token is missing one or more required claims");
|
throw new JWTHeaderException("The token is missing one or more required claims");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->requireIssuer) {
|
if ($this->requireIssuer) {
|
||||||
$hasIssuer = $token->header->has("iss");
|
$hasIssuer = $token->header->has("iss");
|
||||||
if ((!$hasIssuer)
|
if ((!$hasIssuer)
|
||||||
|| (!in_array($token->header->get("iss"), $this->requireIssuer)))
|
|| (!in_array($token->header->get("iss"), $this->requireIssuer)))
|
||||||
throw new JwtTokenException("Invalid issuer");
|
throw new JWTTokenException("Invalid issuer");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->requireAudience) {
|
if ($this->requireAudience) {
|
||||||
$hasAudience = $token->header->has("aud");
|
$hasAudience = $token->header->has("aud");
|
||||||
if ((!$hasAudience)
|
if ((!$hasAudience)
|
||||||
|| (!in_array($token->header->get("aud"), $this->requireAudience)))
|
|| (!in_array($token->header->get("aud"), $this->requireAudience)))
|
||||||
throw new JwtTokenException("Invalid audience");
|
throw new JWTTokenException("Invalid audience");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -74,7 +86,7 @@ class JwtValidator
|
|||||||
|
|
||||||
public function validate(KeyInterface $key, string $raw)
|
public function validate(KeyInterface $key, string $raw)
|
||||||
{
|
{
|
||||||
$token = new JwtToken($key, $raw);
|
$token = new JWTToken($key, $raw);
|
||||||
if ($this->validateToken($token)) {
|
if ($this->validateToken($token)) {
|
||||||
return $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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Validator;
|
|
||||||
|
|
||||||
|
|
||||||
class JwtClaimException extends JwtValidatorException
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Validator;
|
|
||||||
|
|
||||||
class JwtHeaderException extends JwtValidatorException
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Validator;
|
|
||||||
|
|
||||||
class JwtTokenException extends JwtValidatorException
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Validator;
|
|
||||||
|
|
||||||
class JwtValidatorException extends \RuntimeException
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
50
tests/JWTTokenTest.php
Normal file
50
tests/JWTTokenTest.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJWT;
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJWT\Key\JWTPlaintextKey;
|
||||||
|
|
||||||
|
class JWTTokenTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTToken
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTUtil
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTToken
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTUtil
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
22
tests/JWTUtilTest.php
Normal file
22
tests/JWTUtilTest.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJWT;
|
||||||
|
|
||||||
|
|
||||||
|
class JWTUtilTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\JWTUtil::encode
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\JWTUtil::decode
|
||||||
|
*/
|
||||||
|
public function testTheEncodingShouldBeSymmetric()
|
||||||
|
{
|
||||||
|
$v1a = "HelloWorld";
|
||||||
|
$v1b = JWTUtil::encode($v1a);
|
||||||
|
$v1c = JWTUtil::decode($v1b);
|
||||||
|
|
||||||
|
$this->assertEquals($v1a, $v1c);
|
||||||
|
$this->assertNotEquals($v1a, $v1b);
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
<?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());
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
43
tests/Key/JWTDerivedKeyTest.php
Normal file
43
tests/Key/JWTDerivedKeyTest.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJWT\Key;
|
||||||
|
|
||||||
|
|
||||||
|
class JWTDerivedKeyTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTDerivedKey
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTDerivedKey
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
tests/Key/JWTPlaintextKeyTest.php
Normal file
19
tests/Key/JWTPlaintextKeyTest.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJWT\Key;
|
||||||
|
|
||||||
|
|
||||||
|
class JWTPlaintextKeyTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
*/
|
||||||
|
public function testThePlaintextKeyShouldBeReturned()
|
||||||
|
{
|
||||||
|
$key = new JWTPlaintextKey("foo");
|
||||||
|
$this->assertEquals("foo", $key->getBinaryKey());
|
||||||
|
|
||||||
|
$key = new JWTPlaintextKey("bar");
|
||||||
|
$this->assertEquals("bar", $key->getBinaryKey());
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
<?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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
<?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());
|
|
||||||
}
|
|
||||||
}
|
|
163
tests/Validator/JWTValidatorTest.php
Normal file
163
tests/Validator/JWTValidatorTest.php
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\SimpleJWT\Validator;
|
||||||
|
|
||||||
|
use NoccyLabs\SimpleJWT\JWTToken;
|
||||||
|
use NoccyLabs\SimpleJWT\Key\JWTPlaintextKey;
|
||||||
|
|
||||||
|
class JWTValidatorTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\JWTToken
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTUtil
|
||||||
|
*/
|
||||||
|
public function testValidTokensShouldPassWithDefaultConfiguration()
|
||||||
|
{
|
||||||
|
$key = new JWTPlaintextKey("key");
|
||||||
|
$token = new JWTToken($key);
|
||||||
|
|
||||||
|
$validator = new JWTValidator();
|
||||||
|
$valid = $validator->validateToken($token);
|
||||||
|
$this->assertEquals(true, $valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\JWTToken
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTUtil
|
||||||
|
*/
|
||||||
|
public function testExpiredTokensShouldFailWithException()
|
||||||
|
{
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\JWTToken
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTUtil
|
||||||
|
* @dataProvider tokenGenerator
|
||||||
|
*/
|
||||||
|
public function testPinningIssuer($issuer,$audience,$key,$token)
|
||||||
|
{
|
||||||
|
$goodIssuer = "a-dom.tld";
|
||||||
|
$jwtKey = new JWTPlaintextKey($key);
|
||||||
|
$jwtToken = new JWTToken($jwtKey, $token);
|
||||||
|
|
||||||
|
$validator = new JWTValidator();
|
||||||
|
$validator->requireIssuer($goodIssuer);
|
||||||
|
if ($goodIssuer != $issuer) {
|
||||||
|
$this->expectException(JWTTokenException::class);
|
||||||
|
}
|
||||||
|
$valid = $validator->validateToken($jwtToken);
|
||||||
|
if ($goodIssuer == $issuer) {
|
||||||
|
$this->assertTrue($valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\JWTToken
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTUtil
|
||||||
|
* @dataProvider tokenGenerator
|
||||||
|
*/
|
||||||
|
public function testPinningAudience($issuer,$audience,$key,$token)
|
||||||
|
{
|
||||||
|
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
|
||||||
|
$jwtKey = new JWTPlaintextKey($key);
|
||||||
|
$jwtToken = new JWTToken($jwtKey, $token);
|
||||||
|
|
||||||
|
$validator = new JWTValidator();
|
||||||
|
$validator->requireAudience($goodAudience);
|
||||||
|
if (!in_array($audience, $goodAudience)) {
|
||||||
|
$this->expectException(JWTTokenException::class);
|
||||||
|
}
|
||||||
|
$valid = $validator->validateToken($jwtToken);
|
||||||
|
if (in_array($audience, $goodAudience)) {
|
||||||
|
$this->assertTrue($valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTValidator
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Key\JWTPlaintextKey
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\JWTToken
|
||||||
|
* @covers \NoccyLabs\SimpleJWT\Validator\JWTTokenException
|
||||||
|
* @covers NoccyLabs\SimpleJWT\Collection\PropertyBag
|
||||||
|
* @covers NoccyLabs\SimpleJWT\JWTUtil
|
||||||
|
* @dataProvider tokenGenerator
|
||||||
|
*/
|
||||||
|
public function testPinningBoth($issuer,$audience,$key,$token)
|
||||||
|
{
|
||||||
|
$goodIssuer = "a-dom.tld";
|
||||||
|
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
|
||||||
|
$jwtKey = new JWTPlaintextKey($key);
|
||||||
|
$jwtToken = new JWTToken($jwtKey, $token);
|
||||||
|
|
||||||
|
$validator = new JWTValidator();
|
||||||
|
$validator->requireIssuer($goodIssuer);
|
||||||
|
$validator->requireAudience($goodAudience);
|
||||||
|
if (($goodIssuer != $issuer) || (!in_array($audience, $goodAudience))) {
|
||||||
|
$this->expectException(JWTTokenException::class);
|
||||||
|
}
|
||||||
|
$valid = $validator->validateToken($jwtToken);
|
||||||
|
if (($goodIssuer == $issuer) && (in_array($audience, $goodAudience))) {
|
||||||
|
$this->assertTrue($valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tokenGenerator()
|
||||||
|
{
|
||||||
|
$keyrand = function () {
|
||||||
|
return substr(sha1(microtime(true).rand(0,65535)), 5, 10);
|
||||||
|
};
|
||||||
|
$token = function ($head,$claims,$key) {
|
||||||
|
$jwtKey = new JWTPlaintextKey($key);
|
||||||
|
$tok = new JWTToken($jwtKey);
|
||||||
|
$tok->header->setAll($head);
|
||||||
|
$tok->claims->setAll($claims);
|
||||||
|
return $tok->getSignedToken();
|
||||||
|
};
|
||||||
|
$row = function ($iss, $aud, array $claims) use ($keyrand, $token) {
|
||||||
|
$key = $keyrand();
|
||||||
|
$jwtKey = new JWTPlaintextKey($key);
|
||||||
|
return [
|
||||||
|
$iss,
|
||||||
|
$aud,
|
||||||
|
$key,
|
||||||
|
$token(['iss'=>$iss, 'aud'=>$aud], $claims, $key),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
$row("a-dom.tld", "a-dom.tld", []),
|
||||||
|
$row("b-dom.tld", "a-dom.tld", []),
|
||||||
|
$row("b-dom.tld", "b-dom.tld", []),
|
||||||
|
$row("a-dom.tld", "app.a-dom.tld", []),
|
||||||
|
$row("a-dom.tld", "app.b-dom.tld", []),
|
||||||
|
$row("", "app.b-dom.tld", []),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,130 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace NoccyLabs\SimpleJwt\Validator;
|
|
||||||
|
|
||||||
use NoccyLabs\SimpleJwt\JwtToken;
|
|
||||||
use NoccyLabs\SimpleJwt\Key\JwtPlaintextKey;
|
|
||||||
|
|
||||||
class JwtValidatorTest extends \PhpUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
public function testValidTokensShouldPassWithDefaultConfiguration()
|
|
||||||
{
|
|
||||||
$key = new JwtPlaintextKey("key");
|
|
||||||
$token = new JwtToken($key);
|
|
||||||
|
|
||||||
$validator = new JwtValidator();
|
|
||||||
$valid = $validator->validateToken($token);
|
|
||||||
$this->assertEquals(true, $valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExpiredTokensShouldFailWithException()
|
|
||||||
{
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider tokenGenerator
|
|
||||||
*/
|
|
||||||
public function testPinningIssuer($issuer,$audience,$key,$token)
|
|
||||||
{
|
|
||||||
$goodIssuer = "a-dom.tld";
|
|
||||||
$jwtKey = new JwtPlaintextKey($key);
|
|
||||||
$jwtToken = new JwtToken($jwtKey, $token);
|
|
||||||
|
|
||||||
$validator = new JwtValidator();
|
|
||||||
$validator->requireIssuer($goodIssuer);
|
|
||||||
if ($goodIssuer != $issuer) {
|
|
||||||
$this->expectException(JwtTokenException::class);
|
|
||||||
}
|
|
||||||
$valid = $validator->validateToken($jwtToken);
|
|
||||||
if ($goodIssuer == $issuer) {
|
|
||||||
$this->assertTrue($valid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider tokenGenerator
|
|
||||||
*/
|
|
||||||
public function testPinningAudience($issuer,$audience,$key,$token)
|
|
||||||
{
|
|
||||||
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
|
|
||||||
$jwtKey = new JwtPlaintextKey($key);
|
|
||||||
$jwtToken = new JwtToken($jwtKey, $token);
|
|
||||||
|
|
||||||
$validator = new JwtValidator();
|
|
||||||
$validator->requireAudience($goodAudience);
|
|
||||||
if (!in_array($audience, $goodAudience)) {
|
|
||||||
$this->expectException(JwtTokenException::class);
|
|
||||||
}
|
|
||||||
$valid = $validator->validateToken($jwtToken);
|
|
||||||
if (in_array($audience, $goodAudience)) {
|
|
||||||
$this->assertTrue($valid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider tokenGenerator
|
|
||||||
*/
|
|
||||||
public function testPinningBoth($issuer,$audience,$key,$token)
|
|
||||||
{
|
|
||||||
$goodIssuer = "a-dom.tld";
|
|
||||||
$goodAudience = [ "a-dom.tld", "app.a-dom.tld" ];
|
|
||||||
$jwtKey = new JwtPlaintextKey($key);
|
|
||||||
$jwtToken = new JwtToken($jwtKey, $token);
|
|
||||||
|
|
||||||
$validator = new JwtValidator();
|
|
||||||
$validator->requireIssuer($goodIssuer);
|
|
||||||
$validator->requireAudience($goodAudience);
|
|
||||||
if (($goodIssuer != $issuer) || (!in_array($audience, $goodAudience))) {
|
|
||||||
$this->expectException(JwtTokenException::class);
|
|
||||||
}
|
|
||||||
$valid = $validator->validateToken($jwtToken);
|
|
||||||
if (($goodIssuer == $issuer) && (in_array($audience, $goodAudience))) {
|
|
||||||
$this->assertTrue($valid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function tokenGenerator()
|
|
||||||
{
|
|
||||||
$keyrand = function () {
|
|
||||||
return substr(sha1(microtime(true).rand(0,65535)), 5, 10);
|
|
||||||
};
|
|
||||||
$token = function ($head,$claims,$key) {
|
|
||||||
$jwtKey = new JwtPlaintextKey($key);
|
|
||||||
$tok = new JwtToken($jwtKey);
|
|
||||||
$tok->header->setAll($head);
|
|
||||||
$tok->claims->setAll($claims);
|
|
||||||
return $tok->getSignedToken();
|
|
||||||
};
|
|
||||||
$row = function ($iss, $aud, array $claims) use ($keyrand, $token) {
|
|
||||||
$key = $keyrand();
|
|
||||||
$jwtKey = new JwtPlaintextKey($key);
|
|
||||||
return [
|
|
||||||
$iss,
|
|
||||||
$aud,
|
|
||||||
$key,
|
|
||||||
$token(['iss'=>$iss, 'aud'=>$aud], $claims, $key),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
|
||||||
$row("a-dom.tld", "a-dom.tld", []),
|
|
||||||
$row("b-dom.tld", "a-dom.tld", []),
|
|
||||||
$row("b-dom.tld", "b-dom.tld", []),
|
|
||||||
$row("a-dom.tld", "app.a-dom.tld", []),
|
|
||||||
$row("a-dom.tld", "app.b-dom.tld", []),
|
|
||||||
$row("", "app.b-dom.tld", []),
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Reference in New Issue
Block a user