Skip to content

Commit 09437dc

Browse files
committed
feature symfony#53682 [Security] Support RSA algorithm signature for OIDC tokens (Spomky)
This PR was merged into the 7.1 branch. Discussion ---------- [Security] Support RSA algorithm signature for OIDC tokens | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | | License | MIT Add support for RSA signature algorithm for OidcTokenHandler. Amazon Cognito uses RS256 algorithm for its tokens. Commits ------- 3109350 [Security] Support multiple signature algorithms and JWK/JWKSet for OIDC tokens
2 parents cce36cd + 3109350 commit 09437dc

File tree

12 files changed

+297
-92
lines changed

12 files changed

+297
-92
lines changed

composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,7 @@
158158
"twig/cssinliner-extra": "^2.12|^3",
159159
"twig/inky-extra": "^2.12|^3",
160160
"twig/markdown-extra": "^2.12|^3",
161-
"web-token/jwt-checker": "^3.1",
162-
"web-token/jwt-signature-algorithm-ecdsa": "^3.1"
161+
"web-token/jwt-library": "^3.3.2"
163162
},
164163
"conflict": {
165164
"ext-psr": "<1.1|>=2",

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class UnusedTagsPass implements CompilerPassInterface
8282
'routing.route_loader',
8383
'scheduler.schedule_provider',
8484
'scheduler.task',
85+
'security.access_token_handler.oidc.signature_algorithm',
8586
'security.authenticator.login_linker',
8687
'security.expression_language_provider',
8788
'security.remember_me_handler',

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Mark class `ExpressionCacheWarmer` as `final`
8+
* Support multiple signature algorithms for OIDC Token
9+
* Support JWK or JWKSet for OIDC Token
810

911
7.0
1012
---

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
use Jose\Component\Core\Algorithm;
1515
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
16+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1617
use Symfony\Component\DependencyInjection\ChildDefinition;
1718
use Symfony\Component\DependencyInjection\ContainerBuilder;
1819
use Symfony\Component\DependencyInjection\Exception\LogicException;
19-
use Symfony\Component\DependencyInjection\Reference;
2020

2121
/**
2222
* Configures a token handler for decoding and validating an OIDC token.
@@ -31,22 +31,15 @@ public function create(ContainerBuilder $container, string $id, array|string $co
3131
->replaceArgument(4, $config['claim'])
3232
);
3333

34-
if (!ContainerBuilder::willBeAvailable('web-token/jwt-core', Algorithm::class, ['symfony/security-bundle'])) {
35-
throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "composer require web-token/jwt-core".');
34+
if (!ContainerBuilder::willBeAvailable('web-token/jwt-library', Algorithm::class, ['symfony/security-bundle'])) {
35+
throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-library" is not installed. Try running "composer require web-token/jwt-library".');
3636
}
3737

38-
// @see Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory
39-
// for supported algorithms
40-
if (\in_array($config['algorithm'], ['ES256', 'ES384', 'ES512'], true)) {
41-
$tokenHandlerDefinition->replaceArgument(0, new Reference('security.access_token_handler.oidc.signature.'.$config['algorithm']));
42-
} else {
43-
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))
44-
->replaceArgument(0, $config['algorithm'])
45-
);
46-
}
38+
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))
39+
->replaceArgument(0, $config['algorithms']));
4740

48-
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwk'))
49-
->replaceArgument(0, $config['key'])
41+
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
42+
->replaceArgument(0, $config['keyset'])
5043
);
5144
}
5245

@@ -60,6 +53,37 @@ public function addConfiguration(NodeBuilder $node): void
6053
$node
6154
->arrayNode($this->getKey())
6255
->fixXmlConfig($this->getKey())
56+
->validate()
57+
->ifTrue(static fn ($v) => !isset($v['algorithm']) && !isset($v['algorithms']))
58+
->thenInvalid('You must set either "algorithm" or "algorithms".')
59+
->end()
60+
->validate()
61+
->ifTrue(static fn ($v) => !isset($v['key']) && !isset($v['keyset']))
62+
->thenInvalid('You must set either "key" or "keyset".')
63+
->end()
64+
->beforeNormalization()
65+
->ifTrue(static fn ($v) => isset($v['algorithm']) && \is_string($v['algorithm']))
66+
->then(static function ($v) {
67+
if (isset($v['algorithms'])) {
68+
throw new InvalidConfigurationException('You cannot use both "algorithm" and "algorithms" at the same time.');
69+
}
70+
$v['algorithms'] = [$v['algorithm']];
71+
unset($v['algorithm']);
72+
73+
return $v;
74+
})
75+
->end()
76+
->beforeNormalization()
77+
->ifTrue(static fn ($v) => isset($v['key']) && \is_string($v['key']))
78+
->then(static function ($v) {
79+
if (isset($v['keyset'])) {
80+
throw new InvalidConfigurationException('You cannot use both "key" and "keyset" at the same time.');
81+
}
82+
$v['keyset'] = sprintf('{"keys":[%s]}', $v['key']);
83+
84+
return $v;
85+
})
86+
->end()
6387
->children()
6488
->scalarNode('claim')
6589
->info('Claim which contains the user identifier (e.g.: sub, email..).')
@@ -72,14 +96,23 @@ public function addConfiguration(NodeBuilder $node): void
7296
->arrayNode('issuers')
7397
->info('Issuers allowed to generate the token, for validation purpose.')
7498
->isRequired()
75-
->prototype('scalar')->end()
99+
->scalarPrototype()->end()
76100
->end()
77-
->scalarNode('algorithm')
101+
->arrayNode('algorithm')
78102
->info('Algorithm used to sign the token.')
103+
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "algorithms" option instead.')
104+
->end()
105+
->arrayNode('algorithms')
106+
->info('Algorithms used to sign the token.')
79107
->isRequired()
108+
->scalarPrototype()->end()
80109
->end()
81110
->scalarNode('key')
82111
->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).')
112+
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "keyset" option instead.')
113+
->end()
114+
->scalarNode('keyset')
115+
->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).')
83116
->isRequired()
84117
->end()
85118
->end()
Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +0,0 @@
1-
<?php
2-
3-
/*
4-
* This file is part of the Symfony package.
5-
*
6-
* (c) Fabien Potencier <[email protected]>
7-
*
8-
* For the full copyright and license information, please view the LICENSE
9-
* file that was distributed with this source code.
10-
*/
11-
12-
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
13-
14-
use Jose\Component\Core\Algorithm as AlgorithmInterface;
15-
use Jose\Component\Signature\Algorithm;
16-
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
17-
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
18-
19-
/**
20-
* Creates a signature algorithm for {@see OidcTokenHandler}.
21-
*
22-
* @internal
23-
*/
24-
final class SignatureAlgorithmFactory
25-
{
26-
public static function create(string $algorithm): AlgorithmInterface
27-
{
28-
switch ($algorithm) {
29-
case 'ES256':
30-
case 'ES384':
31-
case 'ES512':
32-
if (!class_exists(Algorithm::class.'\\'.$algorithm)) {
33-
throw new \LogicException(sprintf('You cannot use the "%s" signature algorithm since "web-token/jwt-signature-algorithm-ecdsa" is not installed. Try running "composer require web-token/jwt-signature-algorithm-ecdsa".', $algorithm));
34-
}
35-
36-
$algorithm = Algorithm::class.'\\'.$algorithm;
37-
38-
return new $algorithm();
39-
}
40-
41-
throw new InvalidArgumentException(sprintf('Unsupported signature algorithm "%s". Only ES* algorithms are supported. If you want to use another algorithm, create your TokenHandler as a service.', $algorithm));
42-
}
43-
}

src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14-
use Jose\Component\Core\Algorithm;
14+
use Jose\Component\Core\AlgorithmManager;
15+
use Jose\Component\Core\AlgorithmManagerFactory;
1516
use Jose\Component\Core\JWK;
17+
use Jose\Component\Core\JWKSet;
1618
use Jose\Component\Signature\Algorithm\ES256;
1719
use Jose\Component\Signature\Algorithm\ES384;
1820
use Jose\Component\Signature\Algorithm\ES512;
19-
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory;
21+
use Jose\Component\Signature\Algorithm\PS256;
22+
use Jose\Component\Signature\Algorithm\PS384;
23+
use Jose\Component\Signature\Algorithm\PS512;
24+
use Jose\Component\Signature\Algorithm\RS256;
25+
use Jose\Component\Signature\Algorithm\RS384;
26+
use Jose\Component\Signature\Algorithm\RS512;
2027
use Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor;
2128
use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor;
2229
use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor;
@@ -77,28 +84,56 @@
7784

7885
->set('security.access_token_handler.oidc.jwk', JWK::class)
7986
->abstract()
87+
->deprecate('symfony/security-http', '7.1', 'The "%service_id%" service is deprecated. Please use "security.access_token_handler.oidc.jwkset" instead')
8088
->factory([JWK::class, 'createFromJson'])
8189
->args([
8290
abstract_arg('signature key'),
8391
])
8492

85-
->set('security.access_token_handler.oidc.signature', Algorithm::class)
93+
->set('security.access_token_handler.oidc.jwkset', JWKSet::class)
8694
->abstract()
87-
->factory([SignatureAlgorithmFactory::class, 'create'])
95+
->factory([JWKSet::class, 'createFromJson'])
8896
->args([
89-
abstract_arg('signature algorithm'),
97+
abstract_arg('signature keyset'),
98+
])
99+
100+
->set('security.access_token_handler.oidc.algorithm_manager_factory', AlgorithmManagerFactory::class)
101+
->args([
102+
tagged_iterator('security.access_token_handler.oidc.signature_algorithm'),
103+
])
104+
105+
->set('security.access_token_handler.oidc.signature', AlgorithmManager::class)
106+
->abstract()
107+
->factory([service('security.access_token_handler.oidc.algorithm_manager_factory'), 'create'])
108+
->args([
109+
abstract_arg('signature algorithms'),
90110
])
91111

92112
->set('security.access_token_handler.oidc.signature.ES256', ES256::class)
93-
->parent('security.access_token_handler.oidc.signature')
94-
->args(['index_0' => 'ES256'])
113+
->tag('security.access_token_handler.oidc.signature_algorithm')
95114

96115
->set('security.access_token_handler.oidc.signature.ES384', ES384::class)
97-
->parent('security.access_token_handler.oidc.signature')
98-
->args(['index_0' => 'ES384'])
116+
->tag('security.access_token_handler.oidc.signature_algorithm')
99117

100118
->set('security.access_token_handler.oidc.signature.ES512', ES512::class)
101-
->parent('security.access_token_handler.oidc.signature')
102-
->args(['index_0' => 'ES512'])
119+
->tag('security.access_token_handler.oidc.signature_algorithm')
120+
121+
->set('security.access_token_handler.oidc.signature.RS256', RS256::class)
122+
->tag('security.access_token_handler.oidc.signature_algorithm')
123+
124+
->set('security.access_token_handler.oidc.signature.RS384', RS384::class)
125+
->tag('security.access_token_handler.oidc.signature_algorithm')
126+
127+
->set('security.access_token_handler.oidc.signature.RS512', RS512::class)
128+
->tag('security.access_token_handler.oidc.signature_algorithm')
129+
130+
->set('security.access_token_handler.oidc.signature.PS256', PS256::class)
131+
->tag('security.access_token_handler.oidc.signature_algorithm')
132+
133+
->set('security.access_token_handler.oidc.signature.PS384', PS384::class)
134+
->tag('security.access_token_handler.oidc.signature_algorithm')
135+
136+
->set('security.access_token_handler.oidc.signature.PS512', PS512::class)
137+
->tag('security.access_token_handler.oidc.signature_algorithm')
103138
;
104139
};

0 commit comments

Comments
 (0)