Skip to content

Commit 412271d

Browse files
committed
Also enable for id_token token response type
1 parent 0687d5d commit 412271d

3 files changed

Lines changed: 89 additions & 5 deletions

File tree

docs/6-oidc-upgrade.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,19 @@ honoring it from registration would be a remote code execution vector).
7777
- Clients can now be configured with a new property related to the above:
7878
- Authentication Processing Filters (`authproc`)
7979
- Clients can now be configured to release the user's claims directly in the ID
80-
Token. By default, in the authorization code flow the user's (scope-derived)
80+
Token. By default, whenever an access token is also issued (the authorization
81+
code flow, and the `id_token token` implicit flow) the user's (scope-derived)
8182
claims are only available from the UserInfo endpoint and are not included in the
8283
ID Token. Some clients, however, never call the UserInfo endpoint and rely solely
8384
on the ID Token to obtain user attributes. For such clients, this new per-client
8485
property makes the OP include the user's claims (resolved from the granted
8586
scopes) in the ID Token as well. It is disabled by default, so existing clients
8687
are unaffected; enable it only for clients that need it, as it increases the ID
87-
Token size. For security reasons, it can only be set by an administrator (via the
88-
admin UI / API) and is deliberately never accepted from client-supplied dynamic /
89-
OpenID Federation registration metadata.
88+
Token size. (Note: for the bare `id_token` implicit response type there is no
89+
access token to call UserInfo with, so the claims are already included in the ID
90+
Token regardless of this property.) For security reasons, it can only be set by an
91+
administrator (via the admin UI / API) and is deliberately never accepted from
92+
client-supplied dynamic / OpenID Federation registration metadata.
9093
- Clients can now be configured with a new property related to the above:
9194
- Release user claims in ID Token (`add_claims_to_id_token`)
9295
- The encryption key (used to encrypt / decrypt artifacts like authorization

src/Server/Grants/ImplicitGrant.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Psr\Http\Message\ServerRequestInterface;
1313
use RuntimeException;
1414
use SimpleSAML\Module\oidc\Entities\AccessTokenEntity;
15+
use SimpleSAML\Module\oidc\Entities\ClientEntity;
1516
use SimpleSAML\Module\oidc\Entities\Interfaces\EntityStringRepresentationInterface;
1617
use SimpleSAML\Module\oidc\Entities\UserEntity;
1718
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
@@ -275,10 +276,19 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz
275276
$responseParams['expires_in'] = $accessToken->getExpiryDateTime()->getTimestamp() - time();
276277
}
277278

279+
// Decide whether the user's (scope-derived) claims go into the ID Token. The response-type-driven decision
280+
// (AddClaimsToIdTokenRule) already requests them for response_type=id_token, where there is no access token
281+
// to call the UserInfo endpoint with. For id_token token the claims would otherwise be available only at
282+
// UserInfo, so we additionally honor the per-client, administrator-only `add_claims_to_id_token` option,
283+
// matching the authorization code flow (see TokenResponse::prepareIdTokenExtraParam()).
284+
$client = $authorizationRequest->getClient();
285+
$addClaimsToIdToken = $authorizationRequest->getAddClaimsToIdToken()
286+
|| ($client instanceof ClientEntity && $client->getAddClaimsToIdToken());
287+
278288
$idToken = $this->idTokenBuilder->buildFor(
279289
$user,
280290
$accessToken,
281-
$authorizationRequest->getAddClaimsToIdToken(),
291+
$addClaimsToIdToken,
282292
$addAccessTokenHashToIdToken,
283293
$authorizationRequest->getNonce(),
284294
$authorizationRequest->getAuthTime(),

tests/unit/src/Server/Grants/ImplicitGrantTest.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use SimpleSAML\Module\oidc\Services\IdTokenBuilder;
2424
use SimpleSAML\Module\oidc\Services\LoggerService;
2525
use SimpleSAML\Module\oidc\Utils\RequestParamsResolver;
26+
use SimpleSAML\OpenID\Core\IdToken;
2627

2728
#[CoversClass(ImplicitGrant::class)]
2829
class ImplicitGrantTest extends TestCase
@@ -168,6 +169,76 @@ public function testCanCompleteAuthorizationRequest(): void
168169
);
169170
}
170171

172+
/**
173+
* For id_token token the AddClaimsToIdTokenRule does not request the claims (the rule only matches the exact
174+
* "id_token" response type), but a client configured with the administrator-only `add_claims_to_id_token`
175+
* option still gets the user's claims released in the ID Token.
176+
*/
177+
public function testReleasesUserClaimsInIdTokenWhenClientConfiguredTo(): void
178+
{
179+
$this->authorizationRequestMock->method('getUser')->willReturn($this->userEntityMock);
180+
$this->authorizationRequestMock->method('getRedirectUri')->willReturn('redirectUri');
181+
$this->authorizationRequestMock->method('isAuthorizationApproved')->willReturn(true);
182+
$this->authorizationRequestMock->method('getScopes')->willReturn([$this->scopeEntityMock]);
183+
$this->authorizationRequestMock->method('getClient')->willReturn($this->clientEntityMock);
184+
// Response-type rule did NOT request the claims (e.g. id_token token), but the client opts in.
185+
$this->authorizationRequestMock->method('getAddClaimsToIdToken')->willReturn(false);
186+
$this->clientEntityMock->method('getAddClaimsToIdToken')->willReturn(true);
187+
$this->scopeRepositoryMock->method('finalizeScopes')->willReturn([$this->scopeEntityMock]);
188+
189+
$idTokenMock = $this->createMock(IdToken::class);
190+
$idTokenMock->method('getToken')->willReturn('token');
191+
$this->idTokenBuilderMock->expects($this->once())
192+
->method('buildFor')
193+
->with(
194+
$this->anything(),
195+
$this->anything(),
196+
true, // $addClaimsFromScopes
197+
$this->anything(),
198+
$this->anything(),
199+
$this->anything(),
200+
$this->anything(),
201+
$this->anything(),
202+
)
203+
->willReturn($idTokenMock);
204+
205+
$this->sut()->completeAuthorizationRequest($this->authorizationRequestMock);
206+
}
207+
208+
/**
209+
* When neither the response type nor the client requests it, the user's claims are not released in the ID
210+
* Token (they remain available at the UserInfo endpoint via the issued access token).
211+
*/
212+
public function testDoesNotReleaseUserClaimsInIdTokenByDefault(): void
213+
{
214+
$this->authorizationRequestMock->method('getUser')->willReturn($this->userEntityMock);
215+
$this->authorizationRequestMock->method('getRedirectUri')->willReturn('redirectUri');
216+
$this->authorizationRequestMock->method('isAuthorizationApproved')->willReturn(true);
217+
$this->authorizationRequestMock->method('getScopes')->willReturn([$this->scopeEntityMock]);
218+
$this->authorizationRequestMock->method('getClient')->willReturn($this->clientEntityMock);
219+
$this->authorizationRequestMock->method('getAddClaimsToIdToken')->willReturn(false);
220+
$this->clientEntityMock->method('getAddClaimsToIdToken')->willReturn(false);
221+
$this->scopeRepositoryMock->method('finalizeScopes')->willReturn([$this->scopeEntityMock]);
222+
223+
$idTokenMock = $this->createMock(IdToken::class);
224+
$idTokenMock->method('getToken')->willReturn('token');
225+
$this->idTokenBuilderMock->expects($this->once())
226+
->method('buildFor')
227+
->with(
228+
$this->anything(),
229+
$this->anything(),
230+
false, // $addClaimsFromScopes
231+
$this->anything(),
232+
$this->anything(),
233+
$this->anything(),
234+
$this->anything(),
235+
$this->anything(),
236+
)
237+
->willReturn($idTokenMock);
238+
239+
$this->sut()->completeAuthorizationRequest($this->authorizationRequestMock);
240+
}
241+
171242
public function testCanValidateAuthorizationRequestWithRequestRules(): void
172243
{
173244
$this->markTestIncomplete('RequestRulesManager needs to be refactored so it can be strongly typed.');

0 commit comments

Comments
 (0)