8000 [Security] OAuth2 Introspection Endpoint (RFC7662) · symfony/symfony@04c53b4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 04c53b4

Browse files
committed
[Security] OAuth2 Introspection Endpoint (RFC7662)
In addition to the excellent work of @vincentchalamon #48272, this PR allows getting the data from the OAuth2 Introspection Endpoint. This endpoint is defined in the [RFC7662](https://datatracker.ietf.org/doc/html/rfc7662). It returns the following information that is used to retrieve the user: * If the access token is active * A set of claims that are similar to the OIDC one, including the `sub` or the `username`.
1 parent 36a920e commit 04c53b4

File tree

12 files changed

+481
-64
lines changed
  • DependencyInjection/Security/AccessToken
  • Resources/config
  • Tests
  • Component/Security/Http
  • 12 files changed

    +481
    -64
    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 8000 .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: 36 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -41,6 +41,22 @@ public function create(ContainerBuilder $container, string $id, array|string $co
    4141
    $tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
    4242
    ->replaceArgument(0, $config['keyset'])
    4343
    );
    44+
    45+
    if ($config['encryption']['enabled']) {
    46+
    $algorithmManager = (new ChildDefinition('security.access_token_handler.oidc.encryption'))
    47+
    ->replaceArgument(0, $config['encryption']['algorithms']);
    48+
    $keyset = (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
    49+
    ->replaceArgument(0, $config['encryption']['keyset']);
    50+
    51+
    $tokenHandlerDefinition->addMethodCall(
    52+
    'enabledJweSupport',
    53+
    [
    54+
    $keyset,
    55+
    $algorithmManager,
    56+
    $config['encryption']['enforce'],
    57+
    ]
    58+
    );
    59+
    }
    4460
    }
    4561

    4662
    public function getKey(): string
    @@ -112,9 +128,28 @@ public function addConfiguration(NodeBuilder $node): void
    112128
    ->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "keyset" option instead.')
    113129
    ->end()
    114130
    ->scalarNode('keyset')
    115-
    ->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).')
    131+
    ->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys).')
    116132
    ->isRequired()
    117133
    ->end()
    134+
    ->arrayNode('encryption')
    135+
    ->canBeEnabled()
    136+
    ->children()
    137+
    ->booleanNode('enforce')
    138+
    ->info('When enabled, the token shall be encrypted.')
    139+
    ->defaultFalse()
    140+
    ->end()
    141+
    ->arrayNode('algorithms')
    142+
    ->info('Algorithms used to decrypt the token.')
    143+
    ->isRequired()
    144+
    ->requiresAtLeastOneElement()
    145+
    ->scalarPrototype()->end()
    146+
    ->end()
    147+
    ->scalarNode('keyset')
    148+
    ->info('JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys).')
    149+
    ->isRequired()
    150+
    ->end()
    151+
    ->end()
    152+
    ->end()
    118153
    ->end()
    119154
    ->end()
    120155
    ;

    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
    A93C <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