10000 [Security][SecurityBundle] Add encryption support to OIDC tokens · symfony/symfony@cb70da3 · GitHub
[go: up one dir, main page]

Skip to content

Commit cb70da3

Browse files
committed
[Security][SecurityBundle] Add encryption support to OIDC tokens
The changes add encryption support to OpenID Connect (OIDC) tokens in the Symfony Security Bundle. This is useful in making the application more secure. They also ensure the tokens are correctly decrypted and validated before use. Additionally, tests have been expanded to cover these new scenarios. security:
1 parent ae5843f commit cb70da3

File tree

11 files changed

+482
-63
lines changed

11 files changed

+482
-63
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class UnusedTagsPass implements CompilerPassInterface
8585
'routing.route_loader',
8686
'scheduler.schedule_provider',
8787
'scheduler.task',
88+
'security.access_token_handler.oidc.encryption_algorithm',
8889
'security.access_token_handler.oidc.signature_algorithm',
8990
'security.authenticator.login_linker',
9091
'security.expression_language_provider',

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add `Security::isGrantedForUser()` to test user authorization without relying on the session. For example, users not currently logged in, or while processing a message from a message queue
8+
* Add encryption support to `OidcTokenHandler` (JWE)
89

910
7.2
1011
---

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\ChildDefinition;
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Exception\LogicException;
20+
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;
2021

2122
/**
2223
* Configures a token handler for decoding and validating an OIDC token.
@@ -41,6 +42,22 @@ public function create(ContainerBuilder $container, string $id, array|string $co
4142
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
4243
->replaceArgument(0, $config['keyset'])
4344
);
45+
46+
if ($config['encryption']['enabled'] && method_exists(OidcTokenHandler::class, 'enabledJweSupport')) {
47+
$algorithmManager = (new ChildDefinition('security.access_token_handler.oidc.encryption'))
48+
->replaceArgument(0, $config['encryption']['algorithms']);
49+
$keyset = (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
50+
->replaceArgument(0, $config['encryption']['keyset']);
51+
52+
$tokenHandlerDefinition->addMethodCall(
53+
'enabledJweSupport',
54+
[
55+
$keyset,
56+
$algorithmManager,
57+
$config['encryption']['enforce'],
58+
]
59+
);
60+
}
4461
}
4562

4663
public function getKey(): string
@@ -112,9 +129,28 @@ public function addConfiguration(NodeBuilder $node): void
112129
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "keyset" option instead.')
113130
->end()
114131
->scalarNode('keyset')
115-
->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).')
132+
->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys).')
116133
->isRequired()
117134
->end()
135+
->arrayNode('encryption')
136+
->canBeEnabled()
137+
->children()
138+
->booleanNode('enforce')
139+
->info('When enabled, the token shall be encrypted.')
140+
->defaultFalse()
141+
->end()
142+
-& 10000 gt;arrayNode('algorithms')
143+
->info('Algorithms used to decrypt the token.')
144+
->isRequired()
145+
->requiresAtLeastOneElement()
146+
->scalarPrototype()->end()
147+
->end()
148+
->scalarNode('keyset')
149+
->info('JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys).')
150+
->isRequired()
151+
->end()
152+
->end()
153+
->end()
118154
->end()
119155
->end()
120156
;

src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,29 @@
338338
<xsd:choice maxOccurs="unbounded">
339339
<xsd:element name="issuers" type="oidc_issuers" minOccurs="0" maxOccurs="1" />
340340
<xsd:element name="issuer" type="password_hasher" minOccurs="0" maxOccurs="unbounded" />
341+
<xsd:element name="encryption" type="oidc_encryption" />
341342
</xsd:choice>
342343
<xsd:attribute name="claim" type="xsd:string" />
343344
<xsd:attribute name="audience" type="xsd:string" use="required" />
344345
<xsd:attribute name="algorithm" type="xsd:string" use="required" />
345346
<xsd:attribute name="key" type="xsd:string" use="required" />
346347
</xsd:complexType>
347348

349+
<xsd:complexType name="oidc_encryption">
350+
<xsd:choice maxOccurs="unbounded">
351+
<xsd:element name="algorithms" type="oidc_encryption_algorithms" minOccurs="1" maxOccurs="1" />
352+
</xsd:choice>
353+
<xsd:attribute name="enabled" type="xsd:boolean" />
354+
<xsd:attribute name="enforce" type="xsd:boolean" />
355+
<xsd:attribute name="keyset" type="xsd:string" use="required" />
356+
</xsd:complexType>
357+
358+
<xsd:complexType name="oidc_encryption_algorithms">
359+
<xsd:sequence>
360+
<xsd:element name="algorithm" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
361+
</xsd:sequence>
362+
</xsd:complexType>
363+
348364
<xsd:complexType name="oidc_issuers">
349365
<xsd:sequence>
350366
<xsd:element name="issuer" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
use Jose\Component\Core\AlgorithmManagerFactory;
1616
use Jose\Component\Core\JWK;
1717
use Jose\Component\Core\JWKSet;
18+
use Jose\Component\Encryption\Algorithm\ContentEncryption\A128CBCHS256;
19+
use Jose\Component\Encryption\Algorithm\ContentEncryption\A128GCM;
20+
use Jose\Component\Encryption\Algorithm\ContentEncryption\A192CBCHS384;
21+
use Jose\Component\Encryption\Algorithm\ContentEncryption\A192GCM;
22+
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
23+
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256GCM;
24+
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHES;
25+
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHSS;
26+
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP;
1827
use Jose\Component\Signature\Algorithm\ES256;
1928
use Jose\Component\Signature\Algorithm\ES384;
2029
use Jose\Component\Signature\Algorithm\ES512;
@@ -135,5 +144,47 @@
135144

136145
->set('security.access_token_handler.oidc.signature.PS512', PS512::class)
137146
->tag('security.access_token_handler.oidc.signature_algorithm')
147+
148+
// Encryption
149+
// Note that - all xxxKW algorithms are not defined as an extra dependency is required
150+
// - The RSA_1.5 is missing as deprecated
151+
->set('security.access_token_handler.oidc.encryption_algorithm_manager_factory', AlgorithmManagerFactory::class)
152+
->args([
153+
tagged_iterator('security.access_token_handler.oidc.encryption_algorithm'),
154+
])
155+
156+
->set('security.access_token_handler.oidc.encryption', AlgorithmManager::class)
157+
->abstract()
158+
->factory([service('security.access_token_handler.oidc.encryption_algorithm_manager_factory'), 'create'])
159+
->args([
160+
abstract_arg('encryption algorithms'),
161+
])
162+
163+
->set('security.access_token_handler.oidc.encryption.RSAOAEP', RSAOAEP::class)
164+
->tag('security.access_token_handler.oidc.encryption_algorithm')
165+
166+
->set('security.access_token_handler.oidc.encryption.ECDHES', ECDHES::class)
167+
->tag('security.access_token_handler.oidc.encryption_algorithm')
168+
169+
->set('security.access_token_handler.oidc.encryption.ECDHSS', ECDHSS::class)
170+
->tag('security.access_token_handler.oidc.encryption_algorithm')
171+
172+
->set('security.access_token_handler.oidc.encryption.A128CBCHS256', A128CBCHS256::class)
173+
->tag('security.access_token_handler.oidc.encryption_algorithm')
174+
175+
->set('security.access_token_handler.oidc.encryption.A192CBCHS384', A192CBCHS384::class)
176+
->tag('security.access_token_handler.oidc.encryption_algorithm')
177+
178+
->set('security.access_token_handler.oidc.encryption.A256CBCHS512', A256CBCHS512::class)
179+
->tag('security.access_token_handler.oidc.encryption_algorithm')
180+
181+
->set('security.access_token_handler.oidc.encryption.A128GCM', A128GCM::class)
182+
->tag('security.access_token_handler.oidc.encryption_algorithm')
183+
184+
->set('security.access_token_handler.oidc.encryption.A192GCM', A192GCM::class)
185+
->tag('security.access_token_handler.oidc.encryption_algorithm')
186+
187+
->set('security.access_token_handler.oidc.encryption.A256GCM', A256GCM::class)
188+
->tag('security.access_token_handler.oidc.encryption_algorithm')
138189
;
139190
};

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public function testInvalidOidcTokenHandlerConfigurationKeyMissing()
113113
$factory = new AccessTokenFactory($this->createTokenHandlerFactories());
114114

115115
$this->expectException(InvalidConfigurationException::class);
116-
$this->expectExceptionMessage('The child config "keyset" under "access_token.token_handler.oidc" must be configured: JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).');
116+
$this->expectExceptionMessage('The child config "keyset" under "access_token.token_handler.oidc" must be configured: JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys).');
117117

118118
$this->processConfig($config, $factory);
119119
}
@@ -257,6 +257,88 @@ public function testOidcTokenHandlerConfigurationWithMultipleAlgorithms()
257257
$this->assertEquals($expected, $container->getDefinition('security.access_token_handler.firewall1')->getArguments());
258258
}
259259

260+
public function testOidcTokenHandlerConfigurationWithEncryption()
261+
{
262+
$container = new ContainerBuilder();
263+
$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"}]}';
264+
$config = [
265+
'token_handler' => [
266+
'oidc' => [
267+
'algorithms' => ['RS256', 'ES256'],
268+
'issuers' => ['https://www.example.com'],
269+
'audience' => 'audience',
270+
'keyset' => $jwkset,
271+
'encryption' => [
272+
'enabled' => true,
273+
'keyset' => $jwkset,
274+
'algorithms' => ['RSA-OAEP', 'RSA1_5'],
275+
],
276+
],
277+
],
278+
];
279+
280+
$factory = new AccessTokenFactory($this->createTokenHandlerFactories());
281+
$finalizedConfig = $this->processConfig($config, $factory);
282+
283+
$factory->createAuthenticator($container, 'firewall1', $finalizedConfig, 'userprovider');
284+
285+
$this->assertTrue($container->hasDefinition('security.authenticator.access_token.firewall1'));
286+
$this->assertTrue($container->hasDefinition('security.access_token_handler.firewall1'));
287+
}
288+
289+
public function testInvalidOidcTokenHandlerConfigurationMissingEncryptionKeyset()
290+
{
291+
$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"}]}';
292+
$config = [
293+
'token_handler' => [
294+
'oidc' => [
295+
'algorithms' => ['RS256', 'ES256'],
296+
'issuers' => ['https://www.example.com'],
297+
'audience' => 'audience',
298+
'keyset' => $jwkset,
299+
'encryption' => [
300+
'enabled' => true,
301+
'algorithms' => ['RSA-OAEP', 'RSA1_5'],
302+
],
303+
],
304+
],
305+
];
306+
307+
$factory = new AccessTokenFactory($this->createTokenHandlerFactories());
308+
309+
$this->expectException(InvalidConfigurationException::class);
310+
$this->expectExceptionMessage('The child config "keyset" under "access_token.token_handler.oidc.encryption" must be configured: JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys).');
311+
312+
$this->processConfig($config, $factory);
313+
}
314+
315+
public function testInvalidOidcTokenHandlerConfigurationMissingAlgorithm()
316+
{
317+
$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"}]}';
318+
$config = [
319+
'token_handler' => [
320+
'oidc' => [
321+
'algorithms' => ['RS256', 'ES256'],
322+
'issuers' => ['https://www.example.com'],
323+
'audience' => 'audience',
324+
'keyset' => $jwkset,
325+
'encryption' => [
326+
'enabled' => true,
327+
'keyset' => $jwkset,
328+
'algorithms' => [],
329+
],
330+
],
331+
],
332+
];
333+
334+
$factory = new AccessTokenFactory($this->createTokenHandlerFactories());
335+
336+
$this->expectException(InvalidConfigurationException::class);
337+
$this->expectExceptionMessage('The path "access_token.token_handler.oidc.encryption.algorithms" should have at least 1 element(s) defined.');
338+
339+
$this->processConfig($config, $factory);
340+
}
341+
260342
public function testOidcUserInfoTokenHandlerConfigurationWithExistingClient()
261343
{
262344
$container = new ContainerBuilder();

0 commit comments

Comments
 (0)
0