1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\User\Service; |
4
|
|
|
|
5
|
|
|
use Exception; |
6
|
|
|
|
7
|
|
|
// From psr/http-message (PSR-7) |
8
|
|
|
use Psr\Http\Message\ServerRequestInterface as Request; |
9
|
|
|
|
10
|
|
|
// From lcobucci/jwt |
11
|
|
|
use Lcobucci\JWT\Builder; |
12
|
|
|
use Lcobucci\JWT\Parser; |
13
|
|
|
use Lcobucci\JWT\Signer\Rsa\Sha256; |
14
|
|
|
use Lcobucci\JWT\Token; |
15
|
|
|
use Lcobucci\JWT\ValidationData; |
16
|
|
|
|
17
|
|
|
use Charcoal\User\Config\JWTConfig; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Helper class to generate tokens for user and retrieve tokens from request. |
21
|
|
|
*/ |
22
|
|
|
class JWTHandler |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* @var Builder |
26
|
|
|
*/ |
27
|
|
|
private $builder; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var \Lcobucci\JWT\Signer |
31
|
|
|
*/ |
32
|
|
|
private $signer; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var Parser |
36
|
|
|
*/ |
37
|
|
|
private $parser; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var ValidationData |
41
|
|
|
*/ |
42
|
|
|
private $validationData; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var string |
46
|
|
|
*/ |
47
|
|
|
private $privateKey; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
private $publicKey; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @param JWTConfig $config The auth / JWT configuration. |
56
|
|
|
*/ |
57
|
|
|
public function __construct(JWTConfig $config) |
58
|
|
|
{ |
59
|
|
|
$this->builder = $this->createBuilder($config); |
60
|
|
|
$this->signer = $this->createSigner(); |
61
|
|
|
$this->parser = $this->createParser(); |
62
|
|
|
$this->validationData = $this->createValidationData($config); |
63
|
|
|
$this->privateKey = $this->loadPrivateKey($config); |
64
|
|
|
$this->publicKey = $this->loadPublicKey($config); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Builds and signs a token with a "uid" claim. |
69
|
|
|
* |
70
|
|
|
* @param string $userId The user to generate the token for. |
71
|
|
|
* @return Token |
72
|
|
|
*/ |
73
|
|
|
public function generateTokenForUserId($userId) |
74
|
|
|
{ |
75
|
|
|
return $this->builder |
|
|
|
|
76
|
|
|
->set('uid', $userId) |
77
|
|
|
->sign($this->signer, $this->privateKey) |
78
|
|
|
->getToken(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Retrieves, parses and validates the token from request's HTTP_AUTHORIZATION header. |
83
|
|
|
* |
84
|
|
|
* @param Request $request A PSR-7 Request. |
85
|
|
|
* @throws Exception If there is no authorization headers in request or the token is invalid. |
86
|
|
|
* @return Token |
87
|
|
|
*/ |
88
|
|
|
public function getTokenFromRequest(Request $request) |
89
|
|
|
{ |
90
|
|
|
$headers = $request->getHeaders(); |
91
|
|
|
if (!isset($headers['HTTP_AUTHORIZATION'])) { |
92
|
|
|
throw new Exception( |
93
|
|
|
'No authorization (HTTP_AUTHORIZATION) in request headers.' |
94
|
|
|
); |
95
|
|
|
} |
96
|
|
|
$bearer = str_replace('Bearer ', '', $headers['HTTP_AUTHORIZATION'][0]); |
97
|
|
|
$token = $this->parser->parse($bearer); |
98
|
|
|
if ($this->isTokenValid($token) === false) { |
99
|
|
|
throw new Exception( |
100
|
|
|
'Invalid JWT token.' |
101
|
|
|
); |
102
|
|
|
} |
103
|
|
|
return $token; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Validates and verifies a token. |
108
|
|
|
* |
109
|
|
|
* @param Token $token The token to validate and verify. |
110
|
|
|
* @return boolean |
111
|
|
|
*/ |
112
|
|
|
public function isTokenValid(Token $token) |
113
|
|
|
{ |
114
|
|
|
if ($token->validate($this->validationData) !== true) { |
|
|
|
|
115
|
|
|
return false; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
if ($token->verify($this->signer, $this->publicKey) !== true) { |
|
|
|
|
119
|
|
|
return false; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
return true; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Retrieves the uid claim (user id) from a token. |
127
|
|
|
* |
128
|
|
|
* @param Token $token The Token to load the user from. |
129
|
|
|
* @throws Exception If the token does not have a user (uid claim) or the user can not be loaded. |
130
|
|
|
* @return string |
131
|
|
|
*/ |
132
|
|
|
public function getUserIdFromToken(Token $token) |
133
|
|
|
{ |
134
|
|
|
if ($token->getClaim('uid') === null) { |
|
|
|
|
135
|
|
|
throw new Exception( |
136
|
|
|
'Invalid Token. No user (uid claim).' |
137
|
|
|
); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $token->getClaim('uid'); |
|
|
|
|
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @param JWTConfig $config The JWT / auth configuration. |
145
|
|
|
* @return Builder |
146
|
|
|
*/ |
147
|
|
|
private function createBuilder(JWTConfig $config) |
148
|
|
|
{ |
149
|
|
|
$builder = new Builder(); |
150
|
|
|
$builder |
|
|
|
|
151
|
|
|
->setIssuer($config['issuer']) |
152
|
|
|
->setAudience($config['audience']) |
153
|
|
|
->setId($config['id'], true) |
154
|
|
|
->setIssuedAt(time()) |
155
|
|
|
->setNotBefore(time()) |
156
|
|
|
->setExpiration((time() + $config['expiration'])); |
157
|
|
|
return $builder; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @return Parser |
162
|
|
|
*/ |
163
|
|
|
private function createParser() |
164
|
|
|
{ |
165
|
|
|
return new Parser(); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @return \Lcobucci\JWT\Signer |
170
|
|
|
*/ |
171
|
|
|
private function createSigner() |
172
|
|
|
{ |
173
|
|
|
return new Sha256(); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @param JWTConfig $config The JWT / auth configuration. |
178
|
|
|
* @return ValidationData |
179
|
|
|
*/ |
180
|
|
|
private function createValidationData(JWTConfig $config) |
181
|
|
|
{ |
182
|
|
|
$validationData = new ValidationData(); |
|
|
|
|
183
|
|
|
$validationData->setIssuer($config['issuer']); |
184
|
|
|
$validationData->setAudience($config['audience']); |
185
|
|
|
$validationData->setId($config['id']); |
186
|
|
|
return $validationData; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* @param JWTConfig $config The JWT / auth configuration. |
191
|
|
|
* @throws Exception If the key is not set in config or not a string. |
192
|
|
|
* @return string |
193
|
|
|
*/ |
194
|
|
View Code Duplication |
private function loadPrivateKey(JWTConfig $config) |
|
|
|
|
195
|
|
|
{ |
196
|
|
|
if (!isset($config['privateKey'])) { |
197
|
|
|
throw new Exception( |
198
|
|
|
'JWT authentication configuration requires a private key.' |
199
|
|
|
); |
200
|
|
|
} |
201
|
|
|
$keyFile = $config['privateKey']; |
202
|
|
|
if (!file_exists($keyFile)) { |
203
|
|
|
throw new Exception( |
204
|
|
|
sprintf('JWT private key file "%s" does not exist.', $keyFile) |
205
|
|
|
); |
206
|
|
|
} |
207
|
|
|
return file_get_contents($keyFile); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* @param JWTConfig $config The JWT / auth configuration. |
212
|
|
|
* @throws Exception If the key is not set in config or not a string. |
213
|
|
|
* @return string |
214
|
|
|
*/ |
215
|
|
View Code Duplication |
private function loadPublicKey(JWTConfig $config) |
|
|
|
|
216
|
|
|
{ |
217
|
|
|
if (!isset($config['publicKey'])) { |
218
|
|
|
throw new Exception( |
219
|
|
|
'JWT authentication configuration requires a public key.' |
220
|
|
|
); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$keyFile = $config['publicKey']; |
224
|
|
|
if (!file_exists($keyFile)) { |
225
|
|
|
throw new Exception( |
226
|
|
|
sprintf('JWT public key file "%s" does not exist.', $keyFile) |
227
|
|
|
); |
228
|
|
|
} |
229
|
|
|
return file_get_contents($keyFile); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.