Skip to content

Commit 4f2c2f1

Browse files
committed
Add support for handleJweResponse
1 parent fa1729f commit 4f2c2f1

File tree

1 file changed

+100
-67
lines changed

1 file changed

+100
-67
lines changed

src/OpenIDConnectClient.php

Lines changed: 100 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,6 @@ public function __construct(?string $provider_url = null, ?string $client_id = n
341341
],
342342
[
343343
$jwsTokenSupport
344-
//new JWETokenSupport(), // Currently not supported in this library
345344
]
346345
);
347346
}
@@ -418,17 +417,19 @@ public function authenticate(): bool
418417

419418
$id_token = $token_json->id_token;
420419

421-
$jws = $this->jwsSerializerManager->unserialize($id_token);
422-
420+
$idTokenHeaders = $this->decodeJWT($id_token);
423421

424-
if ($jws->getSignature(0)->hasProtectedHeaderParameter('enc')) {
425-
// Handle JWE; Throw exception as JWE is not supported in this library
426-
// @TODO: What should we do with the result?
427-
$this->handleJweResponse($id_token);
422+
if (isset($idTokenHeaders->enc)) {
423+
// Handle JWE
424+
$id_token = $this->handleJweResponse($id_token);
428425
}
429426

427+
$jws = $this->jwsSerializerManager->unserialize($id_token);
428+
429+
//dd($jws);
430+
430431
// Verify header
431-
$this->headerCheckerManager->check($jws, 0);
432+
$this->headerCheckerManager->check($jws, 0, ['alg']);
432433

433434
// Verify the signature
434435
$this->verifySignatures($jws);
@@ -479,16 +480,17 @@ public function authenticate(): bool
479480
// Cleanup state
480481
$this->unsetState();
481482

482-
$jws = $this->jwsSerializerManager->unserialize($id_token);
483+
$idTokenHeaders = $this->decodeJWT($id_token);
483484

484-
if ($jws->getSignature(0)->hasProtectedHeaderParameter('enc')) {
485-
// Handle JWE; Throw exception as JWE is not supported in this library
486-
// @TODO: What should we do with the result?
487-
$this->handleJweResponse($id_token);
485+
if (isset($idTokenHeaders->enc)) {
486+
// Handle JWE
487+
$id_token = $this->handleJweResponse($id_token);
488488
}
489489

490+
$jws = $this->jwsSerializerManager->unserialize($id_token);
491+
490492
// Verify header
491-
$this->headerCheckerManager->check($jws, 0);
493+
$this->headerCheckerManager->check($jws, 0, ['alg']);
492494

493495
// Verify the signature
494496
$this->verifySignatures($jws);
@@ -597,16 +599,17 @@ public function verifyLogoutToken(): bool
597599
if (isset($_REQUEST['logout_token'])) {
598600
$logout_token = $_REQUEST['logout_token'];
599601

600-
$jws = $this->jwsSerializerManager->unserialize($logout_token);
602+
$logoutTokenHeaders = $this->decodeJWT($logout_token);
601603

602-
if ($jws->getSignature(0)->hasProtectedHeaderParameter('enc')) {
603-
// Handle JWE; Throw exception as JWE is not supported in this library
604-
// @TODO: What should we do with the result?
605-
$this->handleJweResponse($logout_token);
604+
if (isset($logoutTokenHeaders->enc)) {
605+
// Handle JWE
606+
$logout_token = $this->handleJweResponse($logout_token);
606607
}
607608

609+
$jws = $this->jwsSerializerManager->unserialize($logout_token);
610+
608611
// Verify header
609-
$this->headerCheckerManager->check($jws, 0);
612+
$this->headerCheckerManager->check($jws, 0, ['alg']);
610613

611614
// Verify the signature
612615
$this->verifySignatures($jws);
@@ -1331,6 +1334,21 @@ public function requestUserInfo(?string $attribute = null)
13311334
throw new OpenIDConnectClientException('The communication to retrieve user data has failed with status code '.$response->getStatus());
13321335
}
13331336

1337+
$userInfo = $this->getClaimsFromUserInfoResponse($response);
1338+
1339+
if ($attribute === null) {
1340+
return $userInfo;
1341+
}
1342+
1343+
if (property_exists($userInfo, $attribute)) {
1344+
return $userInfo->$attribute;
1345+
}
1346+
1347+
return null;
1348+
}
1349+
1350+
private function getClaimsFromUserInfoResponse(Response $response)
1351+
{
13341352
// When we receive application/jwt, the UserInfo Response is signed and/or encrypted.
13351353

13361354
/*
@@ -1347,69 +1365,84 @@ public function requestUserInfo(?string $attribute = null)
13471365

13481366
if ($contentType === 'application/jwt') {
13491367

1350-
$jws = $this->jwsSerializerManager->unserialize($response->getBody());
1368+
$jwt = $response->getBody();
13511369

1352-
if ($jws->getSignature(0)->hasProtectedHeaderParameter('enc')) {
1353-
// Handle JWE; Throw exception as JWE is not supported in this library
1354-
// @TODO: What should be done with the return value?
1355-
$this->handleJweResponse($response->getBody());
1356-
}
1370+
$headers = $this->decodeJWT($jwt);
13571371

1358-
// Verify header
1359-
$this->headerCheckerManager->check($jws, 0);
1372+
if (isset($headers->enc)) {
1373+
// Handle JWE
1374+
$content = $this->handleJweResponse($jwt);
13601375

1361-
// Verify the signature
1362-
$this->verifySignatures($jws);
1376+
// If nested JWT, header will contain cty
1377+
// See RFC 7519 Section 5.2
1378+
if (isset($headers->cty) && $headers->cty === 'JWT') {
1379+
$jwt = $content;
1380+
} else {
1381+
// If not nested JWT, return the content as is
1382+
return $this->getClaimsFromUnsignedUserInfoResponse($content);
1383+
}
1384+
}
13631385

1364-
// Get claims from JWT
1365-
$claims = json_decode($jws->getPayload());
1386+
$jws = $this->jwsSerializerManager->unserialize($jwt);
13661387

1367-
/*
1368-
* The sub (subject) Claim MUST always be returned in the UserInfo Response.
1369-
* NOTE: Due to the possibility of token substitution attacks (see Section 16.11), the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token.
1370-
* The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; if they do not match, the UserInfo Response values MUST NOT be used.
1371-
*
1372-
* If signed, the UserInfo Response MUST contain the Claims iss (issuer) and aud (audience) as members.
1373-
* The iss value MUST be the OP's Issuer Identifier URL. The aud value MUST be or include the RP's Client ID value.
1374-
*/
1388+
return $this->getClaimsFromSignedUserInfoResponse($jws);
13751389

1376-
(new ClaimCheckerManager(
1377-
[
1378-
new AudienceChecker($this->clientID),
1379-
new IssuerChecker($this),
1380-
new IsEqualChecker('sub', $this->getIdTokenPayload()->sub),
1381-
]
1382-
))->check((array) $claims, ['sub', 'aud', 'iss']);
13831390
} else {
1384-
$claims = json_decode($response->getBody());
1391+
return $this->getClaimsFromUnsignedUserInfoResponse($response->getBody());
1392+
}
1393+
}
13851394

1386-
/*
1387-
* The sub (subject) Claim MUST always be returned in the UserInfo Response.
1388-
* NOTE: Due to the possibility of token substitution attacks (see Section 16.11), the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token.
1389-
* The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; if they do not match, the UserInfo Response values MUST NOT be used.
1390-
*/
1395+
private function getClaimsFromUnsignedUserInfoResponse($content)
1396+
{
1397+
$claims = json_decode($content);
13911398

1392-
(new ClaimCheckerManager(
1393-
[
1394-
new IsEqualChecker('sub', $this->getIdTokenPayload()->sub),
1395-
]
1396-
))->check((array) $claims, ['sub']);
1397-
}
1399+
/*
1400+
* The sub (subject) Claim MUST always be returned in the UserInfo Response.
1401+
* NOTE: Due to the possibility of token substitution attacks (see Section 16.11), the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token.
1402+
* The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; if they do not match, the UserInfo Response values MUST NOT be used.
1403+
*/
13981404

1405+
(new ClaimCheckerManager(
1406+
[
1407+
new IsEqualChecker('sub', $this->getIdTokenPayload()->sub),
1408+
]
1409+
))->check((array) $claims, ['sub']);
13991410

1400-
$userInfo = $claims;
1411+
return $claims;
1412+
}
14011413

1402-
if ($attribute === null) {
1403-
return $userInfo;
1404-
}
1414+
private function getClaimsFromSignedUserInfoResponse(JWS $jws)
1415+
{
1416+
// Verify header
1417+
$this->headerCheckerManager->check($jws, 0, ['alg']);
14051418

1406-
if (property_exists($userInfo, $attribute)) {
1407-
return $userInfo->$attribute;
1408-
}
1419+
// Verify the signature
1420+
$this->verifySignatures($jws);
14091421

1410-
return null;
1422+
// Get claims from JWT
1423+
$claims = json_decode($jws->getPayload());
1424+
1425+
/*
1426+
* The sub (subject) Claim MUST always be returned in the UserInfo Response.
1427+
* NOTE: Due to the possibility of token substitution attacks (see Section 16.11), the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token.
1428+
* The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; if they do not match, the UserInfo Response values MUST NOT be used.
1429+
*
1430+
* If signed, the UserInfo Response MUST contain the Claims iss (issuer) and aud (audience) as members.
1431+
* The iss value MUST be the OP's Issuer Identifier URL. The aud value MUST be or include the RP's Client ID value.
1432+
*/
1433+
1434+
(new ClaimCheckerManager(
1435+
[
1436+
new AudienceChecker($this->clientID),
1437+
new IssuerChecker($this),
1438+
new IsEqualChecker('sub', $this->getIdTokenPayload()->sub),
1439+
]
1440+
))->check((array) $claims, ['sub', 'aud', 'iss']);
1441+
1442+
return $claims;
14111443
}
14121444

1445+
14131446
/**
14141447
*
14151448
* @param string|null $attribute optional

0 commit comments

Comments
 (0)