Skip to content

Commit 40cf00f

Browse files
committed
Add support for handleJweResponse
1 parent fa1729f commit 40cf00f

File tree

1 file changed

+98
-67
lines changed

1 file changed

+98
-67
lines changed

src/OpenIDConnectClient.php

Lines changed: 98 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,17 @@ 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+
430429
// Verify header
431-
$this->headerCheckerManager->check($jws, 0);
430+
$this->headerCheckerManager->check($jws, 0, ['alg']);
432431

433432
// Verify the signature
434433
$this->verifySignatures($jws);
@@ -479,16 +478,17 @@ public function authenticate(): bool
479478
// Cleanup state
480479
$this->unsetState();
481480

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

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);
483+
if (isset($idTokenHeaders->enc)) {
484+
// Handle JWE
485+
$id_token = $this->handleJweResponse($id_token);
488486
}
489487

488+
$jws = $this->jwsSerializerManager->unserialize($id_token);
489+
490490
// Verify header
491-
$this->headerCheckerManager->check($jws, 0);
491+
$this->headerCheckerManager->check($jws, 0, ['alg']);
492492

493493
// Verify the signature
494494
$this->verifySignatures($jws);
@@ -597,16 +597,17 @@ public function verifyLogoutToken(): bool
597597
if (isset($_REQUEST['logout_token'])) {
598598
$logout_token = $_REQUEST['logout_token'];
599599

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

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);
602+
if (isset($logoutTokenHeaders->enc)) {
603+
// Handle JWE
604+
$logout_token = $this->handleJweResponse($logout_token);
606605
}
607606

607+
$jws = $this->jwsSerializerManager->unserialize($logout_token);
608+
608609
// Verify header
609-
$this->headerCheckerManager->check($jws, 0);
610+
$this->headerCheckerManager->check($jws, 0, ['alg']);
610611

611612
// Verify the signature
612613
$this->verifySignatures($jws);
@@ -1331,6 +1332,21 @@ public function requestUserInfo(?string $attribute = null)
13311332
throw new OpenIDConnectClientException('The communication to retrieve user data has failed with status code '.$response->getStatus());
13321333
}
13331334

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

13361352
/*
@@ -1347,69 +1363,84 @@ public function requestUserInfo(?string $attribute = null)
13471363

13481364
if ($contentType === 'application/jwt') {
13491365

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

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-
}
1368+
$headers = $this->decodeJWT($jwt);
13571369

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

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

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

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-
*/
1386+
return $this->getClaimsFromSignedUserInfoResponse($jws);
13751387

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']);
13831388
} else {
1384-
$claims = json_decode($response->getBody());
1389+
return $this->getClaimsFromUnsignedUserInfoResponse($response->getBody());
1390+
}
1391+
}
13851392

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-
*/
1393+
private function getClaimsFromUnsignedUserInfoResponse($content)
1394+
{
1395+
$claims = json_decode($content);
13911396

1392-
(new ClaimCheckerManager(
1393-
[
1394-
new IsEqualChecker('sub', $this->getIdTokenPayload()->sub),
1395-
]
1396-
))->check((array) $claims, ['sub']);
1397-
}
1397+
/*
1398+
* The sub (subject) Claim MUST always be returned in the UserInfo Response.
1399+
* 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.
1400+
* 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.
1401+
*/
13981402

1403+
(new ClaimCheckerManager(
1404+
[
1405+
new IsEqualChecker('sub', $this->getIdTokenPayload()->sub),
1406+
]
1407+
))->check((array) $claims, ['sub']);
13991408

1400-
$userInfo = $claims;
1409+
return $claims;
1410+
}
14011411

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

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

1410-
return null;
1420+
// Get claims from JWT
1421+
$claims = json_decode($jws->getPayload());
1422+
1423+
/*
1424+
* The sub (subject) Claim MUST always be returned in the UserInfo Response.
1425+
* 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.
1426+
* 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.
1427+
*
1428+
* If signed, the UserInfo Response MUST contain the Claims iss (issuer) and aud (audience) as members.
1429+
* The iss value MUST be the OP's Issuer Identifier URL. The aud value MUST be or include the RP's Client ID value.
1430+
*/
1431+
1432+
(new ClaimCheckerManager(
1433+
[
1434+
new AudienceChecker($this->clientID),
1435+
new IssuerChecker($this),
1436+
new IsEqualChecker('sub', $this->getIdTokenPayload()->sub),
1437+
]
1438+
))->check((array) $claims, ['sub', 'aud', 'iss']);
1439+
1440+
return $claims;
14111441
}
14121442

1443+
14131444
/**
14141445
*
14151446
* @param string|null $attribute optional

0 commit comments

Comments
 (0)