127 lines
5.2 KiB
PHP

<?php
namespace Lexik\Bundle\JWTAuthenticationBundle\Services\WebToken;
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManager;
use Jose\Bundle\JoseFramework\Services\ClaimCheckerManagerFactory;
use Jose\Bundle\JoseFramework\Services\HeaderCheckerManager;
use Jose\Bundle\JoseFramework\Services\JWELoader;
use Jose\Bundle\JoseFramework\Services\JWELoaderFactory;
use Jose\Bundle\JoseFramework\Services\JWSLoader;
use Jose\Bundle\JoseFramework\Services\JWSLoaderFactory;
use Jose\Component\Checker\InvalidClaimException;
use Jose\Component\Checker\MissingMandatoryClaimException;
use Jose\Component\Core\JWKSet;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
final class AccessTokenLoader
{
private $jwsLoader;
private $jwsHeaderCheckerManager;
private $claimCheckerManager;
private $jweLoader;
private $signatureKeyset;
private $encryptionKeyset;
/**
* @var string[]
*/
private $mandatoryClaims;
private $continueOnDecryptionFailure;
public function __construct(
JWSLoaderFactory $jwsLoaderFactory,
?JWELoaderFactory $jweLoaderFactory,
ClaimCheckerManagerFactory $claimCheckerManagerFactory,
array $claimChecker,
array $jwsHeaderChecker,
array $mandatoryClaims,
array $signatureAlgorithms,
string $signatureKeyset,
?bool $continueOnDecryptionFailure,
?array $jweHeaderChecker,
?array $keyEncryptionAlgorithms,
?array $contentEncryptionAlgorithms,
?string $encryptionKeyset
) {
$this->jwsLoader = $jwsLoaderFactory->create(['jws_compact'], $signatureAlgorithms, $jwsHeaderChecker);
if ($jweLoaderFactory !== null && $keyEncryptionAlgorithms !== null && $contentEncryptionAlgorithms !== null && $jweHeaderChecker !== null) {
$this->jweLoader = $jweLoaderFactory->create(['jwe_compact'], array_merge($keyEncryptionAlgorithms, $contentEncryptionAlgorithms), null, null, $jweHeaderChecker);
$this->continueOnDecryptionFailure = $continueOnDecryptionFailure;
}
$this->signatureKeyset = JWKSet::createFromJson($signatureKeyset);
$this->encryptionKeyset = $encryptionKeyset ? JWKSet::createFromJson($encryptionKeyset) : null;
$this->claimCheckerManager = $claimCheckerManagerFactory->create($claimChecker);
$this->mandatoryClaims = $mandatoryClaims;
}
public function load(string $token): array
{
$token = $this->loadJWE($token);
$data = $this->loadJWS($token);
try {
$this->claimCheckerManager->check($data, $this->mandatoryClaims);
} catch (MissingMandatoryClaimException|InvalidClaimException $e) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, $e->getMessage(), $e, $data);
} catch (\Throwable $e) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Unable to load the token', $e, $data);
}
return $data;
}
/**
* @return array<string, mixed>
*/
private function loadJWS(string $token): array
{
$payload = null;
$data = null;
$signature = null;
try {
$jws = $this->jwsLoader->loadAndVerifyWithKeySet($token, $this->signatureKeyset, $signature);
} catch (\Throwable $e) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be loaded or the signature cannot be verified.');
}
if ($signature !== 0) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token shall contain only one signature.');
}
$payload = $jws->getPayload();
if (!$payload) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
}
$data = json_decode($payload, true);
if (!is_array($data)) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid payload. The token shall contain claims.');
}
return $data;
}
private function loadJWE(string $token): string
{
if (!$this->jweLoader) {
return $token;
}
$recipient = null;
try {
$jwe = $this->jweLoader->loadAndDecryptWithKeySet($token, $this->encryptionKeyset, $recipient);
} catch (\Throwable $e) {
if ($this->continueOnDecryptionFailure === true) {
return $token;
}
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token cannot be decrypted.', $e);
}
$token = $jwe->getPayload();
if (!$token || $recipient !== 0) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid token. The token has no valid content.');
}
return $token;
}
}