Skip to content

Commit d3f86d0

Browse files
committed
feature #60660 [Security] Add security:oidc-token:generate command (Jean-Beru)
This PR was squashed before being merged into the 7.4 branch. Discussion ---------- [Security] Add security:oidc-token:generate command | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | no | New feature? | yes (should I create the CHANGELOG-7.4.md file?) | Deprecations? | no | Issues | | License | MIT The `OidcTokenHandler`, [introduced in Symfony 6.3](https://symfony.com/blog/new-in-symfony-6-3-openid-connect-token-handler), allows to decode a JWT token, validate it and retrieve the user info from it. This pull request introduces a new Symfony command, `bin/console security:oidc-token:generate`, designed to facilitate the generation of JWTs. It can be useful for generating a token for testing purpose. ### Argument and options ``` Description: Generate an OIDC token for a given user Usage: security:oidc-token:generate [options] [--] <user-identifier> Arguments: user-identifier User identifier Options: --firewall=FIREWALL Firewall --algorithm=ALGORITHM Algorithm name to use to sign --issuer=ISSUER Set the Issuer claim (iss) --ttl=TTL Set the Expiration Time claim (exp) (time to live in seconds) --not-before=NOT-BEFORE Set the Not Before claim (nbf) ``` ### Usage ```bash php bin/console security:oidc-token:generate [email protected] \ --firewall="api" \ --algorithm="HS256" \ --issuer="https://example.com" \ --ttl=7200 \ --not-before=tomorrow ``` > [!TIP] > When there is only one value, both "firewall", "algorithm" and "issuer" are not required.. Commits ------- 5c18b1acaa5 [Security] Add security:oidc-token:generate command
2 parents 337d912 + 6f656b0 commit d3f86d0

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Exception\LogicException;
2020
use Symfony\Component\DependencyInjection\Reference;
21+
use Symfony\Component\Security\Http\Command\OidcTokenGenerateCommand;
2122
use Symfony\Contracts\HttpClient\HttpClientInterface;
2223

2324
/**
@@ -79,6 +80,33 @@ public function create(ContainerBuilder $container, string $id, array|string $co
7980
]
8081
);
8182
}
83+
84+
// Generate command
85+
if (!class_exists(OidcTokenGenerateCommand::class)) {
86+
return;
87+
}
88+
89+
if (!$container->hasDefinition('security.access_token_handler.oidc.command.generate')) {
90+
$container
91+
->register('security.access_token_handler.oidc.command.generate', OidcTokenGenerateCommand::class)
92+
->addTag('console.command')
93+
;
94+
}
95+
96+
$firewall = substr($id, strlen('security.access_token_handler.'));
97+
$container->getDefinition('security.access_token_handler.oidc.command.generate')
98+
->addMethodCall('addGenerator', [
99+
$firewall,
100+
(new ChildDefinition('security.access_token_handler.oidc.generator'))
101+
->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))->replaceArgument(0, $config['algorithms']))
102+
->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset'))->replaceArgument(0, $config['keyset']))
103+
->replaceArgument(2, $config['audience'])
104+
->replaceArgument(3, $config['issuers'])
105+
->replaceArgument(4, $config['claim']),
106+
$config['algorithms'],
107+
$config['issuers'],
108+
])
109+
;
82110
}
83111

84112
public function getKey(): string

Resources/config/security_authenticator_access_token.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@
3737
use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor;
3838
use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor;
3939
use Symfony\Component\Security\Http\AccessToken\OAuth2\Oauth2TokenHandler;
40+
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenGenerator;
4041
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
4142
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcUserInfoTokenHandler;
4243
use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor;
4344
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
45+
use Symfony\Component\Security\Http\Command\OidcTokenGenerateCommand;
4446
use Symfony\Contracts\HttpClient\HttpClientInterface;
4547

4648
return static function (ContainerConfigurator $container) {
@@ -200,5 +202,16 @@
200202
service('http_client'),
201203
service('logger')->nullOnInvalid(),
202204
])
205+
206+
->set('security.access_token_handler.oidc.generator', OidcTokenGenerator::class)
207+
->abstract()
208+
->args([
209+
abstract_arg('signature algorithm'),
210+
abstract_arg('signature key'),
211+
abstract_arg('audience'),
212+
abstract_arg('issuers'),
213+
abstract_arg('claim'),
214+
service('clock'),
215+
])
203216
;
204217
};

Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\DependencyInjection\ContainerBuilder;
2828
use Symfony\Component\DependencyInjection\Exception\LogicException;
2929
use Symfony\Component\DependencyInjection\Reference;
30+
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenGenerator;
3031
use Symfony\Contracts\HttpClient\HttpClientInterface;
3132

3233
class AccessTokenFactoryTest extends TestCase
@@ -596,4 +597,52 @@ private function createTokenHandlerFactories(): array
596597
new OAuth2TokenHandlerFactory(),
597598
];
598599
}
600+
601+
public function testOidcTokenGenerator()
602+
{
603+
if (!class_exists(OidcTokenGenerator::class)) {
604+
$this->markTestSkipped('OidcTokenGenerator not available.');
605+
}
606+
607+
$container = new ContainerBuilder();
608+
$jwkset = '{"keys":[{"kty":"EC","crv":"P-256","x":"FtgMtrsKDboRO-Zo0XC7tDJTATHVmwuf9GK409kkars","y":"rWDE0ERU2SfwGYCo1DWWdgFEbZ0MiAXLRBBOzBgs_jY","d":"4G7bRIiKih0qrFxc0dtvkHUll19tTyctoCR3eIbOrO0"},{"kty":"EC","crv":"P-256","x":"0QEAsI1wGI-dmYatdUZoWSRWggLEpyzopuhwk-YUnA4","y":"KYl-qyZ26HobuYwlQh-r0iHX61thfP82qqEku7i0woo","d":"iA_TV2zvftni_9aFAQwFO_9aypfJFCSpcCyevDvz220"}]}';
609+
$config = [
610+
'token_handler' => [
611+
'oidc' => [
612+
'algorithms' => ['RS256', 'ES256'],
613+
'issuers' => ['https://www.example.com'],
614+
'audience' => 'audience',
615+
'keyset' => $jwkset,
616+
],
617+
],
618+
];
619+
620+
$factory = new AccessTokenFactory($this->createTokenHandlerFactories());
621+
$finalizedConfig = $this->processConfig($config, $factory);
622+
623+
$factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider');
624+
625+
$this->assertTrue($container->hasDefinition('security.access_token_handler.oidc.command.generate'));
626+
$this->assertTrue($container->getDefinition('security.access_token_handler.oidc.command.generate')->hasMethodCall('addGenerator'));
627+
}
628+
629+
public function testOidcTokenGeneratorCommandWithNoTokenHandler()
630+
{
631+
$container = new ContainerBuilder();
632+
$config = [
633+
'token_handler' => [
634+
'oidc_user_info' => [
635+
'base_uri' => 'https://www.example.com/realms/demo/protocol/openid-connect/userinfo',
636+
'client' => 'oidc.client',
637+
],
638+
],
639+
];
640+
641+
$factory = new AccessTokenFactory($this->createTokenHandlerFactories());
642+
$finalizedConfig = $this->processConfig($config, $factory);
643+
644+
$factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider');
645+
646+
$this->assertFalse($container->hasDefinition('security.access_token_handler.oidc.command.generate'));
647+
}
599648
}

0 commit comments

Comments
 (0)