Skip to content

Commit

Permalink
Merge pull request #2 from toyokumo/chore/upgrade_web-token_jwt-frame…
Browse files Browse the repository at this point in the history
…work

chore: upgrade web-token/jwt-framework 3.4
  • Loading branch information
syunta authored May 29, 2024
2 parents d3ce7a8 + c58f928 commit 357ffba
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 41 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"symfony/config": "^4.3|~5.0|~6.0",
"symfony/dependency-injection": "^4.4|~5.0|~6.0",
"sensio/framework-extra-bundle": "~3.0|~4.0|~5.0|~6.0",
"web-token/jwt-framework": "^2.2"
"web-token/jwt-framework": "^3.4"
}
,
"require-dev": {
Expand Down
121 changes: 82 additions & 39 deletions src/JWTService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@

use Exception;
use InvalidArgumentException;
use Jose\Component\Checker\AlgorithmChecker;
use Jose\Component\Checker\ClaimCheckerManager;
use Jose\Component\Checker\ExpirationTimeChecker;
use Jose\Component\Checker\HeaderCheckerManager;
use Jose\Component\Checker\IssuedAtChecker;
use Jose\Component\Checker\NotBeforeChecker;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\Util\JsonConverter;
use Jose\Component\Signature\Algorithm\HS256;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\JWSTokenSupport;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\JWSSerializerManager;
use Toyokumo\JWTBundle\Exception\InvalidJWTException;
use Toyokumo\JWTBundle\Exception\NotVerifiedJWTException;
use Jose\Component\Checker\InvalidClaimException;
use Jose\Component\Checker\InvalidHeaderException;
use Jose\Component\Core\JWKSet;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Easy\Build;
use Jose\Easy\Load;

/**
* Class JWTService
Expand All @@ -22,6 +33,18 @@ class JWTService
{
private JWKSet $jwkSet;

private JWSBuilder $jwsBuilder;

private CompactSerializer $compactSerializer;

private JWSVerifier $jwsVerifier;

private JWSSerializerManager $serializerManager;

private HeaderCheckerManager $headerCheckerManager;

private ClaimCheckerManager $claimCheckerManager;

/**
* JWTService constructor.
* @param string $keyDirPath
Expand Down Expand Up @@ -58,6 +81,33 @@ public function __construct(string $keyDirPath, array $jwkInfos)
}
}
$this->jwkSet = new JWKSet($jwks);
$this->jwsBuilder = new JWSBuilder(new AlgorithmManager([
new HS256()
]));
$this->compactSerializer = new CompactSerializer();
$this->jwsVerifier = new JWSVerifier(new AlgorithmManager([
new HS256()
]));
$this->serializerManager = new JWSSerializerManager([
new CompactSerializer()
]);
// https://web-token.spomky-labs.com/the-components/header-checker#header-checker-manager
$this->headerCheckerManager = new HeaderCheckerManager([
new AlgorithmChecker(['HS256']),
// We want to verify that the header "alg" (algorithm)
// is present and contains "HS256"
],
[
new JWSTokenSupport(), // Adds JWS token type support
]);
// https://web-token.spomky-labs.com/the-components/claim-checker#claim-checker-manager
$this->claimCheckerManager = new ClaimCheckerManager(
[
new IssuedAtChecker(),
new NotBeforeChecker(),
new ExpirationTimeChecker(),
]
);
}

/**
Expand All @@ -74,16 +124,19 @@ public function generateJWSToken(
$now = time();

$jwk = $this->jwkSet->get($kid);
$jws = Build::jws()
->alg($jwk->get('alg'))
->header('kid', $kid)
->exp($now + $exp)
->iat($now)
->nbf($now);
foreach ($claims as $key => $value) {
$jws->claim($key, $value);
}
return $jws->sign($jwk);

$claims['iat'] = $now;
$claims['nbf'] = $now;
$claims['exp'] = $now + $exp;
$payload = JsonConverter::encode($claims);

$jws = $this->jwsBuilder
->create()
->withPayload($payload)
->addSignature($jwk, ['alg' => $jwk->get('alg'), 'kid' => $kid] )
->build();

return $this->compactSerializer->serialize($jws);
}

/**
Expand All @@ -97,39 +150,29 @@ public function generateJWSToken(
public function extractValueFromToken(string $token, string $claimKey)
{
try {
// Get kid for identifying jwk
$signatures = (new CompactSerializer())
->unserialize($token)
->getSignatures();
$jws = $this->serializerManager->unserialize($token);
// header validation
$this->headerCheckerManager->check($jws, 0, ['alg', 'kid']);
// payload validation
$claims = JsonConverter::decode($jws->getPayload());
$this->claimCheckerManager->check($claims);
// signature validation
$signatures = $jws->getSignatures();
$signature = $signatures[0];
if (!$signature->hasProtectedHeaderParameter('kid')) {
throw new NotVerifiedJWTException('Token is not verified.');
}
$kid = $signature->getProtectedHeaderParameter('kid');
if (!$this->jwkSet->has($kid)) {
$jwk = $this->jwkSet->get($kid);
$isVerified = $this->jwsVerifier->verifyWithKey($jws, $jwk, 0);
if (!$isVerified) {
throw new NotVerifiedJWTException('Token is not verified.');
}
$jwk = $this->jwkSet->get($kid);

$jwt = Load::jws($token)
->alg($jwk->get('alg'))
->exp()
->nbf()
->key($jwk)
->run();
} catch (InvalidClaimException $e) {
// token expiration etc..
throw new InvalidJWTException('Token is invalid.');
} catch (InvalidHeaderException $e) {
// alg=none tampering etc..
throw new NotVerifiedJWTException('Token is not verified.');
} catch (InvalidArgumentException $e) {
if ($e->getMessage() === 'Unsupported input') {
// failed to decode token
// 表記揺れがあるので str_contains で対応
if (str_contains($e->getMessage(), 'Unsupported input')) {
throw new NotVerifiedJWTException('Token is not verified.');
}
if ($e->getMessage() === 'Undefined index') {
// there is no JWK corresponding to kid

// 表記揺れがあるので str_contains で対応
if (str_contains($e->getMessage(), 'Undefined index')) {
throw new NotVerifiedJWTException('Token is not verified.');
}
throw $e;
Expand All @@ -140,6 +183,6 @@ public function extractValueFromToken(string $token, string $claimKey)
throw $e;
}

return $jwt->claims->get($claimKey);
return $claims[$claimKey];
}
}
3 changes: 2 additions & 1 deletion tests/JWTServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Toyokumo\JWTBundle;

use Jose\Component\Checker\InvalidClaimException;
use PHPUnit\Framework\TestCase;
use Toyokumo\JWTBundle\Exception\InvalidJWTException;
use Toyokumo\JWTBundle\Exception\NotVerifiedJWTException;
Expand Down Expand Up @@ -49,7 +50,7 @@ public function testExpireJWSToken(): void
{
$token = $this->jwt->generateJWSToken(['hoge' => 'fuga'], 'test_key', -1); // exp = -1 means expire right now

$this->expectException(InvalidJWTException::class);
$this->expectException(InvalidClaimException::class);
$this->jwt->extractValueFromToken($token, 'hoge');
}

Expand Down

0 comments on commit 357ffba

Please sign in to comment.