Skip to content

Commit 41843c8

Browse files
Spomkyfabpot
authored andcommitted
[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 97778ec commit 41843c8

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CHANGELOG
1010
erase credentials e.g. using `__serialize()` instead
1111
* Add ability for voters to explain their vote
1212
* Add support for voting on closures
13+
* Add `OAuth2User` with OAuth2 Access Token Introspection support for `OAuth2TokenHandler`
1314

1415
7.2
1516
---

Tests/User/OAuth2UserTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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\Component\Security\Core\Tests\User;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Security\Core\User\OAuth2User;
16+
17+
class OAuth2UserTest extends TestCase
18+
{
19+
public function testCannotCreateUserWithoutSubProperty()
20+
{
21+
$this->expectException(\InvalidArgumentException::class);
22+
$this->expectExceptionMessage('The claim "sub" or "username" must be provided.');
23+
24+
new OAuth2User();
25+
}
26+
27+
public function testCreateFullUserWithAdditionalClaimsUsingPositionalParameters()
28+
{
29+
$this->assertEquals(new OAuth2User(
30+
scope: 'read write dolphin',
31+
username: 'jdoe',
32+
exp: 1419356238,
33+
iat: 1419350238,
34+
sub: 'Z5O3upPC88QrAjx00dis',
35+
aud: 'https://protected.example.net/resource',
36+
iss: 'https://server.example.com/',
37+
client_id: 'l238j323ds-23ij4',
38+
extension_field: 'twenty-seven'
39+
), new OAuth2User(...[
40+
'client_id' => 'l238j323ds-23ij4',
41+
'username' => 'jdoe',
42+
'scope' => 'read write dolphin',
43+
'sub' => 'Z5O3upPC88QrAjx00dis',
44+
'aud' => 'https://protected.example.net/resource',
45+
'iss' => 'https://server.example.com/',
46+
'exp' => 1419356238,
47+
'iat' => 1419350238,
48+
'extension_field' => 'twenty-seven',
49+
]));
50+
}
51+
}

User/OAuth2User.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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\Component\Security\Core\User;
13+
14+
/**
15+
* UserInterface implementation used by the access-token security workflow with an OIDC server.
16+
*/
17+
class OAuth2User implements UserInterface
18+
{
19+
public readonly array $additionalClaims;
20+
21+
public function __construct(
22+
private array $roles = ['ROLE_USER'],
23+
// Standard Claims (https://datatracker.ietf.org/doc/html/rfc7662#section-2.2)
24+
public readonly ?string $scope = null,
25+
public readonly ?string $clientId = null,
26+
public readonly ?string $username = null,
27+
public readonly ?string $tokenType = null,
28+
public readonly ?int $exp = null,
29+
public readonly ?int $iat = null,
30+
public readonly ?int $nbf = null,
31+
public readonly ?string $sub = null,
32+
public readonly ?string $aud = null,
33+
public readonly ?string $iss = null,
34+
public readonly ?string $jti = null,
35+
36+
// Additional Claims ("
37+
// Specific implementations MAY extend this structure with
38+
// their own service-specific response names as top-level members
39+
// of this JSON object.
40+
// ")
41+
...$additionalClaims,
42+
) {
43+
if ((null === $sub || '' === $sub) && (null === $username || '' === $username)) {
44+
throw new \InvalidArgumentException('The claim "sub" or "username" must be provided.');
45+
}
46+
47+
$this->additionalClaims = $additionalClaims['additionalClaims'] ?? $additionalClaims;
48+
}
49+
50+
/**
51+
* OIDC or OAuth specs don't have any "role" notion.
52+
*
53+
* If you want to implement "roles" from your OIDC server,
54+
* send a "roles" constructor argument to this object
55+
* (e.g.: using a custom UserProvider).
56+
*/
57+
public function getRoles(): array
58+
{
59+
return $this->roles;
60+
}
61+
62+
public function getUserIdentifier(): string
63+
{
64+
return (string) ($this->sub ?? $this->username);
65+
}
66+
67+
public function eraseCredentials(): void
68+
{
69+
}
70+
}

0 commit comments

Comments
 (0)