Skip to content

Commit 91baeef

Browse files
author
AKINORI Ishii
committed
Add contents
1 parent 8e2d21e commit 91baeef

File tree

9 files changed

+301
-0
lines changed

9 files changed

+301
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Managed by Composer
2+
/vendor/
3+
4+
# composer.lock
5+
composer.lock
6+
7+
# ideavim
8+
.idea

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,36 @@
11
# JWTBundle
22
Generating and Interpreting JWT token.
3+
4+
## Usage
5+
6+
### JWTService
7+
8+
1. Set JWK information via .yml file.
9+
```
10+
# key_info.yml
11+
parameters:
12+
jwt_keys:
13+
# for HS256
14+
key_name_for_HS256_key:
15+
kid: current_kid
16+
alg: HS256
17+
secret: 'secret string with more than 32 chars.'
18+
# for RS256 or ES256
19+
key_name_for_RS256_or_ES256_key:
20+
kid: current_kid
21+
alg: RS256 or ES256
22+
filename: 'public or private key filename'
23+
passphrase: 'passphrase to decode key'
24+
```
25+
26+
2. Place .yml and key files in the same directory.
27+
28+
3. Set arguments
29+
```
30+
# jwt.yml
31+
services:
32+
Toyokumo\JWTBundle\JWTService:
33+
arguments:
34+
$keyDirPath: 'path/to/key_info.yml and key files'
35+
$jwkInfos: '%jwt_keys%' # JWK information defined in key_info.yml
36+
```

Resources/config/services.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
services:
2+
Toyokumo\JWTBundle\JWTService: ~

composer.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "toyokumo/jwt-bundle",
3+
"description": "Symfony support for generating and interpreting JWT token.",
4+
"keywords": ["symfony", "bundle"],
5+
"homepage": "https://github.com/toyokumo/JWTBundle",
6+
"license": "MIT",
7+
"require": {
8+
"php": "^7.4",
9+
"symfony/config": "~3.0|~4.0",
10+
"symfony/dependency-injection": "~3.0|~4.0",
11+
"sensio/framework-extra-bundle": "~3.0|~4.0|~5.0",
12+
"web-token/jwt-framework": "^2.2"
13+
}
14+
,
15+
"require-dev": {
16+
"roave/security-advisories": "dev-master"
17+
},
18+
"autoload": {
19+
"psr-4": { "Toyokumo\\JWTBundle\\": "src/" }
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Toyokumo\JWTBundle\DependencyInjection;
4+
5+
use Exception;
6+
use Symfony\Component\Config\FileLocator;
7+
use Symfony\Component\DependencyInjection\ContainerBuilder;
8+
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
9+
use Symfony\Component\DependencyInjection\Loader;
10+
11+
class ToyokumoJWTServiceExtension extends Extension
12+
{
13+
/**
14+
* @param array $configs
15+
* @param ContainerBuilder $container
16+
* @throws Exception
17+
*/
18+
public function load(array $configs, ContainerBuilder $container):void
19+
{
20+
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../../Resources/config'));
21+
$loader->load('services.yml');
22+
}
23+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Toyokumo\JWTBundle\Exception;
4+
5+
use Exception;
6+
7+
/**
8+
* Class InvalidJWTException
9+
* Verified JWT containing invalid contents
10+
* ex:
11+
* - exceeding exp claim
12+
* @package AppBundle\Exception
13+
*/
14+
class InvalidJWTException extends Exception
15+
{
16+
/**
17+
* InvalidJWTException constructor.
18+
* @param string $message
19+
* @param int $code
20+
* @param Exception|null $previous
21+
*/
22+
public function __construct(
23+
string $message,
24+
$code = 0,
25+
Exception $previous = null
26+
) {
27+
parent::__construct($message, $code, $previous);
28+
}
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Toyokumo\JWTBundle\Exception;
4+
5+
use Exception;
6+
7+
/**
8+
* Class NotVerifiedJWTException
9+
* Broken JWT
10+
* ex:
11+
* - fail to parse token
12+
* - invalid signature / no signature
13+
* @package AppBundle\Exception
14+
*/
15+
class NotVerifiedJWTException extends Exception
16+
{
17+
/**
18+
* NotVerifiedJWTException constructor.
19+
* @param string $message
20+
* @param int $code
21+
* @param Exception|null $previous
22+
*/
23+
public function __construct(
24+
string $message,
25+
$code = 0,
26+
Exception $previous = null
27+
) {
28+
parent::__construct($message, $code, $previous);
29+
}
30+
}

src/JWTService.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
namespace Toyokumo\JWTBundle;
4+
5+
use Exception;
6+
use InvalidArgumentException;
7+
use Toyokumo\JWTBundle\Exception\InvalidJWTException;
8+
use Toyokumo\JWTBundle\Exception\NotVerifiedJWTException;
9+
use Jose\Component\Checker\InvalidClaimException;
10+
use Jose\Component\Checker\InvalidHeaderException;
11+
use Jose\Component\Core\JWKSet;
12+
use Jose\Component\KeyManagement\JWKFactory;
13+
use Jose\Component\Signature\Serializer\CompactSerializer;
14+
use Jose\Easy\Build;
15+
use Jose\Easy\Load;
16+
17+
/**
18+
* Class JWTService
19+
* @package Toyokumo\JWTBundle
20+
*/
21+
class JWTService
22+
{
23+
private JWKSet $jwkSet;
24+
25+
/**
26+
* JWTService constructor.
27+
* @param string $keyDirPath
28+
* @param array $jwkInfos
29+
*/
30+
public function __construct(string $keyDirPath, array $jwkInfos)
31+
{
32+
if ('/' !== substr($keyDirPath, -1)) {
33+
$keyDirPath .= '/';
34+
}
35+
$jwks = [];
36+
foreach ($jwkInfos as $jwkInfo) {
37+
$kid = $jwkInfo['kid'];
38+
$alg = $jwkInfo['alg'];
39+
if ($alg === 'HS256') {
40+
$secret = $jwkInfo['secret'];
41+
$jwks[] = JWKFactory::createFromSecret($secret, [
42+
'use' => 'sig',
43+
'alg' => $alg,
44+
'kid' => $kid,
45+
]);
46+
} else {
47+
$filename = $jwkInfo['filename'];
48+
$passphrase = $jwkInfo['passphrase'];
49+
$jwks[] = JWKFactory::createFromKeyFile(
50+
$keyDirPath . $filename,
51+
$passphrase,
52+
[
53+
'use' => 'sig',
54+
'alg' => $alg,
55+
'kid' => $kid,
56+
]
57+
);
58+
}
59+
}
60+
$this->jwkSet = new JWKSet($jwks);
61+
}
62+
63+
/**
64+
* @param array $claims
65+
* @param string $kid
66+
* @param int $exp
67+
* @return string
68+
*/
69+
public function generateJWSToken(
70+
array $claims,
71+
string $kid,
72+
int $exp
73+
): string {
74+
$now = time();
75+
76+
$jwk = $this->jwkSet->get($kid);
77+
$jws = Build::jws()
78+
->alg($jwk->get('alg'))
79+
->header('kid', $kid)
80+
->exp($now + $exp)
81+
->iat($now)
82+
->nbf($now);
83+
foreach ($claims as $key => $value) {
84+
$jws->claim($key, $value);
85+
}
86+
return $jws->sign($jwk);
87+
}
88+
89+
/**
90+
* @param string $token
91+
* @param string $claimKey
92+
* @return mixed
93+
* @throws NotVerifiedJWTException
94+
* @throws InvalidJWTException
95+
* @throws Exception
96+
*/
97+
public function extractValueFromToken(string $token, string $claimKey)
98+
{
99+
try {
100+
// Get kid for identifying jwk
101+
$signatures = (new CompactSerializer())
102+
->unserialize($token)
103+
->getSignatures();
104+
$signature = $signatures[0];
105+
if (!$signature->hasProtectedHeaderParameter('kid')) {
106+
throw new NotVerifiedJWTException('Token is not verified.');
107+
}
108+
$kid = $signature->getProtectedHeaderParameter('kid');
109+
if (!$this->jwkSet->has($kid)) {
110+
throw new NotVerifiedJWTException('Token is not verified.');
111+
}
112+
$jwk = $this->jwkSet->get($kid);
113+
114+
$jwt = Load::jws($token)
115+
->alg($jwk->get('alg'))
116+
->exp()
117+
->nbf()
118+
->key($jwk)
119+
->run();
120+
} catch (InvalidClaimException $e) {
121+
// token expiration etc..
122+
throw new InvalidJWTException('Token is invalid.');
123+
} catch (InvalidHeaderException $e) {
124+
// alg=none tampering etc..
125+
throw new NotVerifiedJWTException('Token is not verified.');
126+
} catch (InvalidArgumentException $e) {
127+
if ($e->getMessage() === 'Unsupported input') {
128+
// failed to decode token
129+
throw new NotVerifiedJWTException('Token is not verified.');
130+
}
131+
if ($e->getMessage() === 'Undefined index') {
132+
// there is no JWK corresponding to kid
133+
throw new NotVerifiedJWTException('Token is not verified.');
134+
}
135+
throw $e;
136+
} catch (Exception $e) {
137+
if ($e->getMessage() === 'Invalid signature') {
138+
throw new NotVerifiedJWTException('Token is not verified.');
139+
}
140+
throw $e;
141+
}
142+
143+
return $jwt->claims->get($claimKey);
144+
}
145+
}

src/ToyokumoJWTBundle.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Toyokumo\JWTBundle;
4+
5+
use Symfony\Component\HttpKernel\Bundle\Bundle;
6+
7+
class ToyokumoJWTBundle extends Bundle
8+
{
9+
}

0 commit comments

Comments
 (0)