Added support for validating token issuer and audience

* Use requireIssuer() and requireAudience() on the JwtValidator to make sure
  that the token is for what you expect it to be for.
* A setAll() method has been added to property bag, applying but not overriding
  values.
* Added tests for JwtValidator.
This commit is contained in:
Chris 2021-02-16 18:25:29 +01:00
parent 9d85e2ccef
commit 7753853e58
4 changed files with 96 additions and 5 deletions

View File

@ -79,7 +79,10 @@ Parsing is done by passing the raw token as the 2nd parameter
$validator = new JwtValidator(); $validator = new JwtValidator();
// Require that some claim exists // Require that some claim exists
$validator->addRequiredClaim("some/required/Claim"); $validator
->requireIssuer("api.issuer.tld")
->requireAudience(["api.issuer.tld", "foo.issuer.tld"])
->addRequiredClaim("some/required/Claim");
try { try {
// Pass a JwtToken to validateToken()... // Pass a JwtToken to validateToken()...

View File

@ -45,6 +45,14 @@ class PropertyBag
$this->props[$prop] = $value; $this->props[$prop] = $value;
} }
public function setAll(array $props)
{
$this->props = array_merge(
$this->props,
$props
);
}
/** /**
* 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

View File

@ -11,6 +11,10 @@ class JwtValidator
private $requireClaims = []; private $requireClaims = [];
private $requireIssuer = [];
private $requireAudience = [];
public function __construct() public function __construct()
{ {
$this->requireHeaders = [ $this->requireHeaders = [
@ -27,6 +31,16 @@ class JwtValidator
$this->requireClaims[] = $name; $this->requireClaims[] = $name;
} }
public function requireIssuer($issuer)
{
$this->requireIssuer = (array)$issuer;
}
public function requireAudience($audience)
{
$this->requireAudience = (array)$audience;
}
public function validateToken(JwtToken $token) public function validateToken(JwtToken $token)
{ {
if (!$token->isValid()) { if (!$token->isValid()) {
@ -41,6 +55,20 @@ class JwtValidator
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) {
$hasIssuer = $token->header->has("iss");
if ((!$hasIssuer)
|| (!in_array($token->header->get("iss"), $this->requireIssuer)))
throw new JwtTokenException("Invalid issuer");
}
if ($this->requireAudience) {
$hasAudience = $token->header->has("aud");
if ((!$hasAudience)
|| (!in_array($token->header->get("aud"), $this->requireAudience)))
throw new JwtTokenException("Invalid audience");
}
return true; return true;
} }

View File

@ -8,7 +8,7 @@ use NoccyLabs\SimpleJwt\Key\JwtPlaintextKey;
class JwtValidatorTest extends \PhpUnit\Framework\TestCase class JwtValidatorTest extends \PhpUnit\Framework\TestCase
{ {
public function testValidKeysShouldPassWithDefaultConfiguration() public function testValidTokensShouldPassWithDefaultConfiguration()
{ {
$key = new JwtPlaintextKey("key"); $key = new JwtPlaintextKey("key");
$token = new JwtToken($key); $token = new JwtToken($key);
@ -18,7 +18,7 @@ class JwtValidatorTest extends \PhpUnit\Framework\TestCase
$this->assertEquals(true, $valid); $this->assertEquals(true, $valid);
} }
public function testExpiredKeysShouldFailWithException() public function testExpiredTokensShouldFailWithException()
{ {
$key = new JwtPlaintextKey("key"); $key = new JwtPlaintextKey("key");
$token = new JwtToken($key); $token = new JwtToken($key);
@ -31,4 +31,56 @@ class JwtValidatorTest extends \PhpUnit\Framework\TestCase
$valid = $validator->validateToken($token); $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);
}
}
// public function testPinningAudience()
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", []),
];
}
} }