|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
namespace App\Service\Token; |
|
6
|
|
|
|
|
7
|
|
|
use App\Service\Token\Exception\TokenAudienceException; |
|
8
|
|
|
use App\Service\Token\Exception\TokenExpiredException; |
|
9
|
|
|
use App\Service\Token\Exception\TokenIssuerException; |
|
10
|
|
|
use App\Service\Token\Exception\TokenParseException; |
|
11
|
|
|
use App\Service\Token\Exception\TokenSignatureException; |
|
12
|
|
|
use App\Service\Token\Exception\TokenValidationExceptionInterface; |
|
13
|
|
|
use Lcobucci\JWT\Builder; |
|
14
|
|
|
use Lcobucci\JWT\Parser; |
|
15
|
|
|
use Lcobucci\JWT\Signer\BaseSigner; |
|
16
|
|
|
use Lcobucci\JWT\Signer\Hmac\Sha256; |
|
17
|
|
|
use Lcobucci\JWT\Token; |
|
18
|
|
|
use Lcobucci\JWT\ValidationData; |
|
19
|
|
|
use Ramsey\Uuid\Uuid; |
|
20
|
|
|
|
|
21
|
|
|
class TokenManager |
|
22
|
|
|
{ |
|
23
|
|
|
public const DEFAULT_EXPIRY = 3600; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @var BaseSigner |
|
27
|
|
|
*/ |
|
28
|
|
|
private $signer; |
|
29
|
|
|
/** |
|
30
|
|
|
* @var string|null |
|
31
|
|
|
*/ |
|
32
|
|
|
private $issuer; |
|
33
|
|
|
/** |
|
34
|
|
|
* @var string|null |
|
35
|
|
|
*/ |
|
36
|
|
|
private $audience; |
|
37
|
|
|
/** |
|
38
|
|
|
* @var string |
|
39
|
|
|
*/ |
|
40
|
|
|
private $privateKey; |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* @var int |
|
44
|
|
|
*/ |
|
45
|
|
|
private $defaultExpiry; |
|
46
|
|
|
|
|
47
|
1 |
|
public function __construct( |
|
48
|
|
|
string $privateKey, |
|
49
|
|
|
int $defaultExpiry = self::DEFAULT_EXPIRY, |
|
50
|
|
|
string $issuer = null, |
|
51
|
|
|
string $audience = null |
|
52
|
|
|
) { |
|
53
|
1 |
|
$this->signer = $this->getSigner(); |
|
54
|
1 |
|
$this->issuer = $issuer; |
|
55
|
1 |
|
$this->audience = $audience; |
|
56
|
1 |
|
$this->privateKey = $privateKey; |
|
57
|
1 |
|
$this->defaultExpiry = $defaultExpiry; |
|
58
|
1 |
|
} |
|
59
|
|
|
|
|
60
|
1 |
|
public function getDefaultExpiry(): int |
|
61
|
|
|
{ |
|
62
|
1 |
|
return $this->defaultExpiry; |
|
63
|
|
|
} |
|
64
|
|
|
|
|
65
|
|
|
public function createNewToken(array $customClaims = [], int $expiration = 3600, bool $autoSign = true): Token |
|
66
|
|
|
{ |
|
67
|
|
|
$builder = (new Builder()) |
|
|
|
|
|
|
68
|
|
|
->setId(Uuid::uuid1()->toString(), true) // Configures the id (jti claim), replicating as a header item |
|
69
|
|
|
->setIssuedAt(time()) // Configures the time that the token was issue (iat claim) |
|
70
|
|
|
->setNotBefore(time() + 0) // Configures the time that the token can be used (nbf claim) |
|
71
|
|
|
->setExpiration(time() + $expiration); // Configures the expiration time of the token (exp claim) |
|
72
|
|
|
|
|
73
|
|
|
if ($this->audience !== null) { |
|
74
|
|
|
$builder->setAudience($this->audience); |
|
|
|
|
|
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
if ($this->issuer !== null) { |
|
78
|
|
|
$builder->setIssuer($this->issuer); |
|
|
|
|
|
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
foreach ($customClaims as $key => $value) { |
|
82
|
|
|
$builder->set($key, $value); |
|
|
|
|
|
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
if ($autoSign) { |
|
86
|
|
|
return $this->signToken($builder); |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
return $builder->getToken(); |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
/** |
|
93
|
|
|
* Sign the token. |
|
94
|
|
|
*/ |
|
95
|
|
|
public function signToken(Builder $builder): Token |
|
96
|
|
|
{ |
|
97
|
|
|
return $builder->sign($this->signer, $this->privateKey) |
|
|
|
|
|
|
98
|
|
|
->getToken(); // Retrieves the generated token |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
|
|
/** |
|
102
|
|
|
* @throws TokenParseException |
|
103
|
|
|
*/ |
|
104
|
|
|
public function parseToken(string $tokenString): Token |
|
105
|
|
|
{ |
|
106
|
|
|
$tokenParser = new Parser(); |
|
107
|
|
|
try { |
|
108
|
|
|
return $tokenParser->parse($tokenString); |
|
109
|
|
|
} catch (\Throwable $e) { |
|
110
|
|
|
throw new TokenParseException($e->getMessage()); |
|
111
|
|
|
} |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* Ensure that the token signature is valid and |
|
116
|
|
|
* the token have not been tampered. |
|
117
|
|
|
* |
|
118
|
|
|
* @throws TokenSignatureException |
|
119
|
|
|
*/ |
|
120
|
|
|
public function ensureValidSignature(Token $token): void |
|
121
|
|
|
{ |
|
122
|
|
|
if (!$this->verifySignature($token)) { |
|
123
|
|
|
throw new TokenSignatureException(sprintf( |
|
124
|
|
|
'Token failed signature verification.' |
|
125
|
|
|
)); |
|
126
|
|
|
} |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* @throw TokenExpiredException |
|
131
|
|
|
*/ |
|
132
|
|
|
public function ensureNotExpired(Token $token): void |
|
133
|
|
|
{ |
|
134
|
|
|
if ($this->isExpired($token)) { |
|
135
|
|
|
throw new TokenExpiredException(sprintf( |
|
136
|
|
|
'Token validity has expired.' |
|
137
|
|
|
)); |
|
138
|
|
|
} |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* @throws TokenValidationExceptionInterface the main one |
|
143
|
|
|
* @throws TokenParseException |
|
144
|
|
|
* @throws TokenExpiredException |
|
145
|
|
|
* @throws TokenSignatureException |
|
146
|
|
|
* @throws TokenIssuerException |
|
147
|
|
|
* @throws TokenAudienceException |
|
148
|
|
|
*/ |
|
149
|
|
|
public function getValidatedToken(string $tokenString): Token |
|
150
|
|
|
{ |
|
151
|
|
|
$token = $this->parseToken($tokenString); |
|
152
|
|
|
|
|
153
|
|
|
$this->ensureValidSignature($token); |
|
154
|
|
|
$this->ensureNotExpired($token); |
|
155
|
|
|
|
|
156
|
|
|
$data = new ValidationData(); // It will use the current time to validate (iat, nbf and exp) |
|
157
|
|
|
if ($this->audience !== null) { |
|
158
|
|
|
$data->setAudience($this->audience); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
if ($this->issuer !== null) { |
|
162
|
|
|
$data->setIssuer($this->issuer); |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
// Optionally test for issuer |
|
166
|
|
|
if ($this->issuer !== null) { |
|
167
|
|
|
$issuer = $token->getClaim('iss', null); |
|
168
|
|
|
if ($issuer !== $this->issuer) { |
|
169
|
|
|
throw new TokenIssuerException(sprintf( |
|
170
|
|
|
'Token issuer does not match' |
|
171
|
|
|
)); |
|
172
|
|
|
} |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
// Optionally test for audience |
|
176
|
|
|
if ($this->audience !== null) { |
|
177
|
|
|
$issuer = $token->getClaim('aud', null); |
|
178
|
|
|
if ($issuer !== $this->audience) { |
|
179
|
|
|
throw new TokenAudienceException(sprintf( |
|
180
|
|
|
'Token audience does not match' |
|
181
|
|
|
)); |
|
182
|
|
|
} |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
return $token; |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
1 |
|
public function getSigner(): BaseSigner |
|
189
|
|
|
{ |
|
190
|
1 |
|
return new Sha256(); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
public function verifySignature(Token $token): bool |
|
194
|
|
|
{ |
|
195
|
|
|
return $token->verify($this->signer, $this->privateKey); |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
public function isExpired(Token $token): bool |
|
199
|
|
|
{ |
|
200
|
|
|
return $token->isExpired(); |
|
201
|
|
|
} |
|
202
|
|
|
} |
|
203
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.