Skip to content

Commit c84131b

Browse files
frankdekkerchalasr
authored andcommitted
feat: Add jwt leeway configuration option
1 parent 161ba05 commit c84131b

File tree

8 files changed

+85
-9
lines changed

8 files changed

+85
-9
lines changed

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ For implementation into Symfony projects, please see [bundle documentation](basi
8181
# How to generate a public key: https://oauth2.thephpleague.com/installation/#generating-public-and-private-keys
8282
public_key: ~ # Required, Example: /var/oauth/public.key
8383
84+
# The leeway in seconds to allow for clock skew in JWT verification. Default PT0S (no leeway).
85+
jwt_leeway: null
86+
8487
scopes:
8588
# Scopes that you wish to utilize in your application.
8689
# This should be a simple array of strings.

src/DependencyInjection/Configuration.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ private function createResourceServerNode(): NodeDefinition
131131
->isRequired()
132132
->cannotBeEmpty()
133133
->end()
134+
->scalarNode('jwt_leeway')
135+
->info('The leeway in seconds to allow for clock skew in JWT verification. Default PT0S (no leeway).')
136+
->example('PT60S')
137+
->defaultValue(null)
138+
->end()
134139
->end()
135140
;
136141

src/DependencyInjection/LeagueOAuth2ServerExtension.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use League\Bundle\OAuth2ServerBundle\Service\CredentialsRevokerInterface;
2828
use League\Bundle\OAuth2ServerBundle\ValueObject\Scope as ScopeModel;
2929
use League\OAuth2\Server\AuthorizationServer;
30+
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
3031
use League\OAuth2\Server\CryptKey;
3132
use League\OAuth2\Server\Grant\AuthCodeGrant;
3233
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
@@ -331,6 +332,11 @@ private function configureResourceServer(ContainerBuilder $container, array $con
331332
false,
332333
]))
333334
;
335+
if (null !== $config['jwt_leeway']) {
336+
$container
337+
->findDefinition(BearerTokenValidator::class)
338+
->replaceArgument(1, new Definition(\DateInterval::class, [$config['jwt_leeway']]));
339+
}
334340
}
335341

336342
private function configureScopes(ContainerBuilder $container, array $scopes): void

src/Resources/config/services.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use League\Bundle\OAuth2ServerBundle\Security\EventListener\CheckScopeListener;
3939
use League\Bundle\OAuth2ServerBundle\Service\SymfonyLeagueEventListenerProvider;
4040
use League\OAuth2\Server\AuthorizationServer;
41+
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
4142
use League\OAuth2\Server\EventEmitting\EventEmitter;
4243
use League\OAuth2\Server\Grant\AuthCodeGrant;
4344
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
@@ -153,11 +154,20 @@
153154
->configurator(service(GrantConfigurator::class))
154155
->alias(AuthorizationServer::class, 'league.oauth2_server.authorization_server')
155156

157+
// League bearer token validator
158+
->set('league.oauth2_server.bearer_token_validator', BearerTokenValidator::class)
159+
->args([
160+
service(AccessTokenRepositoryInterface::class),
161+
null,
162+
])
163+
->alias(BearerTokenValidator::class, 'league.oauth2_server.bearer_token_validator')
164+
156165
// League resource server
157166
->set('league.oauth2_server.resource_server', ResourceServer::class)
158167
->args([
159168
service(AccessTokenRepositoryInterface::class),
160169
null,
170+
service(BearerTokenValidator::class),
161171
])
162172
->alias(ResourceServer::class, 'league.oauth2_server.resource_server')
163173

tests/Acceptance/CustomPersistenceManagerTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ protected static function createKernel(array $options = []): KernelInterface
159159
return new TestKernel(
160160
'test',
161161
false,
162+
null,
162163
[
163164
'custom' => [
164165
'access_token_manager' => 'test.access_token_manager',
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace League\Bundle\OAuth2ServerBundle\Tests\Acceptance;
6+
7+
use League\Bundle\OAuth2ServerBundle\Repository\AccessTokenRepository;
8+
use League\Bundle\OAuth2ServerBundle\Tests\TestKernel;
9+
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
10+
use Symfony\Bundle\FrameworkBundle\Console\Application;
11+
use Symfony\Component\HttpKernel\KernelInterface;
12+
13+
class JwtLeewayConfigurationTest extends AbstractAcceptanceTest
14+
{
15+
protected function setUp(): void
16+
{
17+
$this->client = self::createClient();
18+
$this->application = new Application($this->client->getKernel());
19+
}
20+
21+
public function testLeewayConfigurationIsSet(): void
22+
{
23+
/** @var AccessTokenRepository $tokenRepository */
24+
$tokenRepository = $this->client->getContainer()->get(AccessTokenRepository::class);
25+
$validator = $this->client->getContainer()->get(BearerTokenValidator::class);
26+
27+
$expected = new BearerTokenValidator($tokenRepository, new \DateInterval('PT60S'));
28+
$this->assertEquals($expected, $validator);
29+
}
30+
31+
protected static function createKernel(array $options = []): KernelInterface
32+
{
33+
return new TestKernel(
34+
'test',
35+
false,
36+
[
37+
'public_key' => '%env(PUBLIC_KEY_PATH)%',
38+
'jwt_leeway' => 'PT60S',
39+
]
40+
);
41+
}
42+
}

tests/Integration/AuthorizationServerTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,8 +700,8 @@ public function testFailedAuthorizationWithInvalidRedirectUri(): void
700700
$response = $this->handleTokenRequest($request);
701701

702702
// Response assertions.
703-
$this->assertSame('invalid_client', $response['error']);
704-
$this->assertSame('Client authentication failed', $response['error_description']);
703+
$this->assertSame('invalid_request', $response['error']);
704+
$this->assertSame('The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.', $response['error_description']);
705705
}
706706

707707
public function testSuccessfulImplicitRequest(): void

tests/TestKernel.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@
2828

2929
final class TestKernel extends Kernel implements CompilerPassInterface
3030
{
31-
public function __construct(string $environment, bool $debug, private array $persistenceConfig = ['doctrine' => ['entity_manager' => 'default']])
32-
{
31+
public function __construct(
32+
string $environment,
33+
bool $debug,
34+
private ?array $resourceServiceConfig = null,
35+
private ?array $persistenceConfig = null,
36+
) {
3337
parent::__construct($environment, $debug);
3438
}
3539

@@ -52,7 +56,14 @@ public function registerBundles(): iterable
5256

5357
public function getCacheDir(): string
5458
{
55-
return \sprintf('%s/tests/.kernel/cache', $this->getProjectDir());
59+
$cacheDirectory = 'cache';
60+
61+
// create unique cache directory when custom config is provided
62+
if (null !== $this->resourceServiceConfig || null !== $this->persistenceConfig) {
63+
$cacheDirectory = '/cache/' . hash('sha256', serialize(($this->resourceServiceConfig ?? []) + ($this->persistenceConfig ?? [])));
64+
}
65+
66+
return \sprintf('%s/tests/.kernel/' . $cacheDirectory, $this->getProjectDir());
5667
}
5768

5869
public function getLogDir(): string
@@ -163,9 +174,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
163174
'private_key' => '%env(PRIVATE_KEY_PATH)%',
164175
'encryption_key' => '%env(ENCRYPTION_KEY)%',
165176
],
166-
'resource_server' => [
167-
'public_key' => '%env(PUBLIC_KEY_PATH)%',
168-
],
177+
'resource_server' => $this->resourceServiceConfig ?? ['public_key' => '%env(PUBLIC_KEY_PATH)%'],
169178
'scopes' => [
170179
'available' => [
171180
FixtureFactory::FIXTURE_SCOPE_FIRST,
@@ -175,7 +184,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
175184
FixtureFactory::FIXTURE_SCOPE_SECOND,
176185
],
177186
],
178-
'persistence' => $this->persistenceConfig,
187+
'persistence' => $this->persistenceConfig ?? ['doctrine' => ['entity_manager' => 'default']],
179188
]);
180189

181190
$this->configureControllers($container);

0 commit comments

Comments
 (0)