Skip to content

Commit 74344b0

Browse files
committed
feature #214 feat: Add jwt leeway configuration option (frankdekker)
This PR was squashed before being merged into the 1.x-dev branch. Discussion ---------- feat: Add jwt leeway configuration option When a jwt token is created on one server and then used on another, there might be a slight time difference in the jwt token timestamp. However the jwt validation is by default set to `PT0S` leeway and rejects the jwt token. The league/oauth2-server uses lcobucci/jwt for jwt creation and validation. There's an option to set the jwt leeway. See the constructor of https://github.com/thephpleague/oauth2-server/blob/master/src/AuthorizationValidators/BearerTokenValidator.php However there is no way to set this value via the oauth2-server-bundle. This PR allows to set this value. Commits ------- c84131b feat: Add jwt leeway configuration option
2 parents 8dacedf + c84131b commit 74344b0

File tree

7 files changed

+83
-7
lines changed

7 files changed

+83
-7
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;
@@ -152,11 +153,20 @@
152153
->configurator(service(GrantConfigurator::class))
153154
->alias(AuthorizationServer::class, 'league.oauth2_server.authorization_server')
154155

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

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/TestKernel.php

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

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

@@ -51,7 +55,14 @@ public function registerBundles(): iterable
5155

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

5768
public function getLogDir(): string
@@ -158,9 +169,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
158169
'private_key' => '%env(PRIVATE_KEY_PATH)%',
159170
'encryption_key' => '%env(ENCRYPTION_KEY)%',
160171
],
161-
'resource_server' => [
162-
'public_key' => '%env(PUBLIC_KEY_PATH)%',
163-
],
172+
'resource_server' => $this->resourceServiceConfig ?? ['public_key' => '%env(PUBLIC_KEY_PATH)%'],
164173
'scopes' => [
165174
'available' => [
166175
FixtureFactory::FIXTURE_SCOPE_FIRST,
@@ -170,7 +179,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
170179
FixtureFactory::FIXTURE_SCOPE_SECOND,
171180
],
172181
],
173-
'persistence' => $this->persistenceConfig,
182+
'persistence' => $this->persistenceConfig ?? ['doctrine' => ['entity_manager' => 'default']],
174183
]);
175184

176185
$this->configureControllers($container);

0 commit comments

Comments
 (0)