Skip to content

Commit

Permalink
Merge pull request #1304 from ScientaNL/fixes-1021
Browse files Browse the repository at this point in the history
Allow configuring leeway for the validation of jwt token dates
  • Loading branch information
Sephster authored Nov 17, 2022
2 parents 0b32d7b + aeae9e4 commit 4677b2f
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 4 deletions.
16 changes: 12 additions & 4 deletions src/AuthorizationValidators/BearerTokenValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use League\OAuth2\Server\CryptKey;
Expand Down Expand Up @@ -43,12 +43,19 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
*/
private $jwtConfiguration;

/**
* @var \DateInterval|null
*/
private $jwtValidAtDateLeeway;

/**
* @param AccessTokenRepositoryInterface $accessTokenRepository
* @param \DateInterval|null $jwtValidAtDateLeeway
*/
public function __construct(AccessTokenRepositoryInterface $accessTokenRepository)
public function __construct(AccessTokenRepositoryInterface $accessTokenRepository, \DateInterval $jwtValidAtDateLeeway = null)
{
$this->accessTokenRepository = $accessTokenRepository;
$this->jwtValidAtDateLeeway = $jwtValidAtDateLeeway;
}

/**
Expand All @@ -73,10 +80,11 @@ private function initJwtConfiguration()
InMemory::plainText('empty', 'empty')
);

$clock = new SystemClock(new DateTimeZone(\date_default_timezone_get()));
$this->jwtConfiguration->setValidationConstraints(
\class_exists(LooseValidAt::class)
? new LooseValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get())))
: new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))),
? new LooseValidAt($clock, $this->jwtValidAtDateLeeway)
: new ValidAt($clock, $this->jwtValidAtDateLeeway),
new SignedWith(
new Sha256(),
InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '')
Expand Down
63 changes: 63 additions & 0 deletions tests/AuthorizationValidators/BearerTokenValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,67 @@ public function testBearerTokenValidatorRejectsExpiredToken()

$bearerTokenValidator->validateAuthorization($request);
}

public function testBearerTokenValidatorAcceptsExpiredTokenWithinLeeway()
{
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();

// We fake generating this token 10 seconds into the future, an extreme example of possible time drift between servers
$future = (new DateTimeImmutable())->add(new DateInterval('PT10S'));

$bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock, new \DateInterval('PT10S'));
$bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));

$bearerTokenValidatorReflection = new ReflectionClass(BearerTokenValidator::class);
$jwtConfiguration = $bearerTokenValidatorReflection->getProperty('jwtConfiguration');
$jwtConfiguration->setAccessible(true);

$jwtTokenFromFutureWithinLeeway = $jwtConfiguration->getValue($bearerTokenValidator)->builder()
->permittedFor('client-id')
->identifiedBy('token-id')
->issuedAt($future)
->canOnlyBeUsedAfter($future)
->expiresAt((new DateTimeImmutable())->add(new DateInterval('PT1H')))
->relatedTo('user-id')
->withClaim('scopes', 'scope1 scope2 scope3 scope4')
->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key'));

$request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $jwtTokenFromFutureWithinLeeway->toString()));

$validRequest = $bearerTokenValidator->validateAuthorization($request);

$this->assertArrayHasKey('authorization', $validRequest->getHeaders());
}

public function testBearerTokenValidatorRejectsExpiredTokenBeyondLeeway()
{
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();

// We fake generating this token 10 seconds into the future, an extreme example of possible time drift between servers
$future = (new DateTimeImmutable())->add(new DateInterval('PT20S'));

$bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock, new \DateInterval('PT10S'));
$bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));

$bearerTokenValidatorReflection = new ReflectionClass(BearerTokenValidator::class);
$jwtConfiguration = $bearerTokenValidatorReflection->getProperty('jwtConfiguration');
$jwtConfiguration->setAccessible(true);

$jwtTokenFromFutureBeyondLeeway = $jwtConfiguration->getValue($bearerTokenValidator)->builder()
->permittedFor('client-id')
->identifiedBy('token-id')
->issuedAt($future)
->canOnlyBeUsedAfter($future)
->expiresAt((new DateTimeImmutable())->add(new DateInterval('PT1H')))
->relatedTo('user-id')
->withClaim('scopes', 'scope1 scope2 scope3 scope4')
->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key'));

$request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $jwtTokenFromFutureBeyondLeeway->toString()));

$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(9);

$bearerTokenValidator->validateAuthorization($request);
}
}

0 comments on commit 4677b2f

Please sign in to comment.