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:
		@@ -79,7 +79,10 @@ Parsing is done by passing the raw token as the 2nd parameter
 | 
			
		||||
 | 
			
		||||
    $validator = new JwtValidator();
 | 
			
		||||
    // 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 {
 | 
			
		||||
        // Pass a JwtToken to validateToken()...
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,14 @@ class PropertyBag
 | 
			
		||||
        $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.
 | 
			
		||||
     * Use the value() method to get with a default value
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,10 @@ class JwtValidator
 | 
			
		||||
 | 
			
		||||
    private $requireClaims = [];
 | 
			
		||||
 | 
			
		||||
    private $requireIssuer = [];
 | 
			
		||||
 | 
			
		||||
    private $requireAudience = [];
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->requireHeaders = [
 | 
			
		||||
@@ -27,6 +31,16 @@ class JwtValidator
 | 
			
		||||
        $this->requireClaims[] = $name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function requireIssuer($issuer)
 | 
			
		||||
    {
 | 
			
		||||
        $this->requireIssuer = (array)$issuer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function requireAudience($audience)
 | 
			
		||||
    {
 | 
			
		||||
        $this->requireAudience = (array)$audience;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function validateToken(JwtToken $token)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$token->isValid()) {
 | 
			
		||||
@@ -41,6 +55,20 @@ class JwtValidator
 | 
			
		||||
            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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ use NoccyLabs\SimpleJwt\Key\JwtPlaintextKey;
 | 
			
		||||
class JwtValidatorTest extends \PhpUnit\Framework\TestCase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public function testValidKeysShouldPassWithDefaultConfiguration()
 | 
			
		||||
    public function testValidTokensShouldPassWithDefaultConfiguration()
 | 
			
		||||
    {
 | 
			
		||||
        $key = new JwtPlaintextKey("key");
 | 
			
		||||
        $token = new JwtToken($key);
 | 
			
		||||
@@ -18,7 +18,7 @@ class JwtValidatorTest extends \PhpUnit\Framework\TestCase
 | 
			
		||||
        $this->assertEquals(true, $valid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testExpiredKeysShouldFailWithException()
 | 
			
		||||
    public function testExpiredTokensShouldFailWithException()
 | 
			
		||||
    {
 | 
			
		||||
        $key = new JwtPlaintextKey("key");
 | 
			
		||||
        $token = new JwtToken($key);
 | 
			
		||||
@@ -31,4 +31,56 @@ class JwtValidatorTest extends \PhpUnit\Framework\TestCase
 | 
			
		||||
        $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", []),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user