forked from codeigniter4/shield
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathJWTManagerTest.php
More file actions
396 lines (331 loc) · 13.5 KB
/
JWTManagerTest.php
File metadata and controls
396 lines (331 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter Shield.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Tests\Unit\Authentication\JWT;
use CodeIgniter\I18n\Time;
use CodeIgniter\Shield\Authentication\JWTManager;
use CodeIgniter\Shield\Config\AuthJWT;
use CodeIgniter\Shield\Entities\User;
use CodeIgniter\Shield\Models\UserModel;
use PHPUnit\Framework\Attributes\Depends;
use Tests\Support\TestCase;
/**
* @internal
*/
final class JWTManagerTest extends TestCase
{
private function createJWTManager(?Time $clock = null): JWTManager
{
return new JWTManager($clock);
}
public function testGenerateToken(): array
{
/** @var User $user */
$user = fake(UserModel::class, ['id' => 1, 'username' => 'John Smith'], false);
// Fix the current time for testing.
Time::setTestNow('now');
$clock = new Time();
$manager = $this->createJWTManager($clock);
$currentTime = $clock->now();
$token = $manager->generateToken($user);
// Reset the current time.
Time::setTestNow();
$this->assertIsString($token);
$this->assertStringStartsWith('eyJ', $token);
return [$token, $currentTime];
}
#[Depends('testGenerateToken')]
public function testGenerateTokenPayload(array $data): void
{
[$token, $currentTime] = $data;
$manager = $this->createJWTManager();
$payload = $manager->parse($token);
/** @var AuthJWT $config */
$config = config('AuthJWT');
$expected = [
'iss' => $config->defaultClaims['iss'],
'sub' => '1',
'iat' => $currentTime->getTimestamp(),
'exp' => $currentTime->getTimestamp() + $config->timeToLive,
];
$this->assertSame($expected, (array) $payload);
}
public function testGenerateTokenAddClaims(): void
{
/** @var User $user */
$user = fake(UserModel::class, ['id' => 1, 'username' => 'John Smith'], false);
$manager = $this->createJWTManager();
$claims = [
'email' => 'admin@example.jp',
];
$token = $manager->generateToken($user, $claims);
$this->assertIsString($token);
$payload = $this->decodeJWT($token, 'payload');
$this->assertStringStartsWith('1', $payload['sub']);
$this->assertStringStartsWith('admin@example.jp', $payload['email']);
}
public function testIssue(): array
{
// Fix the current time for testing.
Time::setTestNow('now');
$clock = new Time();
$manager = $this->createJWTManager($clock);
$currentTime = $clock->now();
$payload = [
'user_id' => '1',
'email' => 'admin@example.jp',
];
$token = $manager->issue($payload, DAY);
// Reset the current time.
Time::setTestNow();
$this->assertIsString($token);
$this->assertStringStartsWith('eyJ', $token);
return [$token, $currentTime];
}
#[Depends('testIssue')]
public function testIssuePayload(array $data): void
{
[$token, $currentTime] = $data;
$manager = $this->createJWTManager();
$payload = $manager->parse($token);
/** @var AuthJWT $config */
$config = config('AuthJWT');
$expected = [
'iss' => $config->defaultClaims['iss'],
'user_id' => '1',
'email' => 'admin@example.jp',
'iat' => $currentTime->getTimestamp(),
'exp' => $currentTime->getTimestamp() + DAY,
];
$this->assertSame($expected, (array) $payload);
}
public function testIssueSetKid(): void
{
$manager = $this->createJWTManager();
// Set kid
/** @var AuthJWT $config */
$config = config('AuthJWT');
$config->keys['default'][0]['kid'] = 'Key01';
$payload = [
'user_id' => '1',
];
$token = $manager->issue($payload, DAY);
$this->assertIsString($token);
$headers = $this->decodeJWT($token, 'header');
$this->assertSame([
'typ' => 'JWT',
'alg' => 'HS256',
'kid' => 'Key01',
], $headers);
}
public function testIssueAddHeader(): void
{
$manager = $this->createJWTManager();
$payload = [
'user_id' => '1',
];
$headers = [
'extra_key' => 'extra_value',
];
$token = $manager->issue($payload, DAY, 'default', $headers);
$this->assertIsString($token);
$headers = $this->decodeJWT($token, 'header');
$this->assertEqualsCanonicalizing([
'typ' => 'JWT',
'extra_key' => 'extra_value',
'alg' => 'HS256',
], $headers);
}
public function testIssueWithAsymmetricKey(): void
{
$manager = $this->createJWTManager();
/** @var AuthJWT $config */
$config = config('AuthJWT');
$config->keys['default'][0] = [
'alg' => 'RS256', // algorithm.
'public' => '', // Public Key
'private' => <<<'EOD'
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuzWHNM5f+amCjQztc5QTfJfzCC5J4nuW+L/aOxZ4f8J3Frew
M2c/dufrnmedsApb0By7WhaHlcqCh/ScAPyJhzkPYLae7bTVro3hok0zDITR8F6S
JGL42JAEUk+ILkPI+DONM0+3vzk6Kvfe548tu4czCuqU8BGVOlnp6IqBHhAswNMM
78pos/2z0CjPM4tbeXqSTTbNkXRboxjU29vSopcT51koWOgiTf3C7nJUoMWZHZI5
HqnIhPAG9yv8HAgNk6CMk2CadVHDo4IxjxTzTTqo1SCSH2pooJl9O8at6kkRYsrZ
WwsKlOFE2LUce7ObnXsYihStBUDoeBQlGG/BwQIDAQABAoIBAFtGaOqNKGwggn9k
6yzr6GhZ6Wt2rh1Xpq8XUz514UBhPxD7dFRLpbzCrLVpzY80LbmVGJ9+1pJozyWc
VKeCeUdNwbqkr240Oe7GTFmGjDoxU+5/HX/SJYPpC8JZ9oqgEA87iz+WQX9hVoP2
oF6EB4ckDvXmk8FMwVZW2l2/kd5mrEVbDaXKxhvUDf52iVD+sGIlTif7mBgR99/b
c3qiCnxCMmfYUnT2eh7Vv2LhCR/G9S6C3R4lA71rEyiU3KgsGfg0d82/XWXbegJW
h3QbWNtQLxTuIvLq5aAryV3PfaHlPgdgK0ft6ocU2de2FagFka3nfVEyC7IUsNTK
bq6nhAECgYEA7d/0DPOIaItl/8BWKyCuAHMss47j0wlGbBSHdJIiS55akMvnAG0M
39y22Qqfzh1at9kBFeYeFIIU82ZLF3xOcE3z6pJZ4Dyvx4BYdXH77odo9uVK9s1l
3T3BlMcqd1hvZLMS7dviyH79jZo4CXSHiKzc7pQ2YfK5eKxKqONeXuECgYEAyXlG
vonaus/YTb1IBei9HwaccnQ/1HRn6MvfDjb7JJDIBhNClGPt6xRlzBbSZ73c2QEC
6Fu9h36K/HZ2qcLd2bXiNyhIV7b6tVKk+0Psoj0dL9EbhsD1OsmE1nTPyAc9XZbb
OPYxy+dpBCUA8/1U9+uiFoCa7mIbWcSQ+39gHuECgYAz82pQfct30aH4JiBrkNqP
nJfRq05UY70uk5k1u0ikLTRoVS/hJu/d4E1Kv4hBMqYCavFSwAwnvHUo51lVCr/y
xQOVYlsgnwBg2MX4+GjmIkqpSVCC8D7j/73MaWb746OIYZervQ8dbKahi2HbpsiG
8AHcVSA/agxZr38qvWV54QKBgCD5TlDE8x18AuTGQ9FjxAAd7uD0kbXNz2vUYg9L
hFL5tyL3aAAtUrUUw4xhd9IuysRhW/53dU+FsG2dXdJu6CxHjlyEpUJl2iZu/j15
YnMzGWHIEX8+eWRDsw/+Ujtko/B7TinGcWPz3cYl4EAOiCeDUyXnqnO1btCEUU44
DJ1BAoGBAJuPD27ErTSVtId90+M4zFPNibFP50KprVdc8CR37BE7r8vuGgNYXmnI
RLnGP9p3pVgFCktORuYS2J/6t84I3+A17nEoB4xvhTLeAinAW/uTQOUmNicOP4Ek
2MsLL2kHgL8bLTmvXV4FX+PXphrDKg1XxzOYn0otuoqdAQrkK4og
-----END RSA PRIVATE KEY-----
EOD,
];
$payload = [
'user_id' => '1',
];
$token = $manager->issue($payload, DAY);
$this->assertIsString($token);
$headers = $this->decodeJWT($token, 'header');
$this->assertSame([
'typ' => 'JWT',
'alg' => 'RS256',
], $headers);
}
private function decodeJWT(string $token, $part): array
{
$map = [
'header' => 0,
'payload' => 1,
];
$index = $map[$part];
return json_decode(
base64_decode(
str_replace(
'_',
'/',
str_replace(
'-',
'+',
explode('.', $token)[$index],
),
),
true,
),
true,
);
}
public function testParseCanDecodeTokenSignedByOldKey(): void
{
/** @var AuthJWT $config */
$config = config('AuthJWT');
$config->keys['default'] = [
[
'kid' => 'Key01',
'alg' => 'HS256', // algorithm.
'secret' => 'Key01_Secret_at_least_256_bits!!',
],
];
// Generate token with Key01.
$manager = $this->createJWTManager();
$payload = [
'user_id' => '1',
];
$token = $manager->issue($payload, DAY, 'default');
// Add new Key02.
$config->keys['default'] = [
[
'kid' => 'Key02',
'alg' => 'HS256', // algorithm.
'secret' => 'Key02_Secret_at_least_256_bits!!',
],
[
'kid' => 'Key01',
'alg' => 'HS256', // algorithm.
'secret' => 'Key01_Secret_at_least_256_bits!!',
],
];
$payload = $manager->parse($token);
$this->assertSame('1', $payload->user_id);
}
public function testParseCanSpecifyKey(): void
{
/** @var AuthJWT $config */
$config = config('AuthJWT');
$config->keys['mobile'] = [
[
'kid' => 'Key01',
'alg' => 'HS256', // algorithm.
'secret' => 'Key01_Secret_at_least_256_bits!!',
],
];
// Generate token with the mobile key.
$manager = $this->createJWTManager();
$payload = [
'user_id' => '1',
];
$token = $manager->issue($payload, DAY, 'mobile');
$payload = $manager->parse($token, 'mobile');
$this->assertSame('1', $payload->user_id);
}
public function testParseCanDecodeWithAsymmetricKey(): void
{
$token = $this->generateJWTWithAsymmetricKey();
$manager = $this->createJWTManager();
$payload = $manager->parse($token);
$this->assertSame('1', $payload->user_id);
}
private function generateJWTWithAsymmetricKey(): string
{
$manager = $this->createJWTManager();
/** @var AuthJWT $config */
$config = config('AuthJWT');
$config->keys['default'][0] = [
'alg' => 'RS256', // algorithm.
'public' => <<<'EOD'
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzWHNM5f+amCjQztc5QT
fJfzCC5J4nuW+L/aOxZ4f8J3FrewM2c/dufrnmedsApb0By7WhaHlcqCh/ScAPyJ
hzkPYLae7bTVro3hok0zDITR8F6SJGL42JAEUk+ILkPI+DONM0+3vzk6Kvfe548t
u4czCuqU8BGVOlnp6IqBHhAswNMM78pos/2z0CjPM4tbeXqSTTbNkXRboxjU29vS
opcT51koWOgiTf3C7nJUoMWZHZI5HqnIhPAG9yv8HAgNk6CMk2CadVHDo4IxjxTz
TTqo1SCSH2pooJl9O8at6kkRYsrZWwsKlOFE2LUce7ObnXsYihStBUDoeBQlGG/B
wQIDAQAB
-----END PUBLIC KEY-----
EOD,
'private' => <<<'EOD'
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuzWHNM5f+amCjQztc5QTfJfzCC5J4nuW+L/aOxZ4f8J3Frew
M2c/dufrnmedsApb0By7WhaHlcqCh/ScAPyJhzkPYLae7bTVro3hok0zDITR8F6S
JGL42JAEUk+ILkPI+DONM0+3vzk6Kvfe548tu4czCuqU8BGVOlnp6IqBHhAswNMM
78pos/2z0CjPM4tbeXqSTTbNkXRboxjU29vSopcT51koWOgiTf3C7nJUoMWZHZI5
HqnIhPAG9yv8HAgNk6CMk2CadVHDo4IxjxTzTTqo1SCSH2pooJl9O8at6kkRYsrZ
WwsKlOFE2LUce7ObnXsYihStBUDoeBQlGG/BwQIDAQABAoIBAFtGaOqNKGwggn9k
6yzr6GhZ6Wt2rh1Xpq8XUz514UBhPxD7dFRLpbzCrLVpzY80LbmVGJ9+1pJozyWc
VKeCeUdNwbqkr240Oe7GTFmGjDoxU+5/HX/SJYPpC8JZ9oqgEA87iz+WQX9hVoP2
oF6EB4ckDvXmk8FMwVZW2l2/kd5mrEVbDaXKxhvUDf52iVD+sGIlTif7mBgR99/b
c3qiCnxCMmfYUnT2eh7Vv2LhCR/G9S6C3R4lA71rEyiU3KgsGfg0d82/XWXbegJW
h3QbWNtQLxTuIvLq5aAryV3PfaHlPgdgK0ft6ocU2de2FagFka3nfVEyC7IUsNTK
bq6nhAECgYEA7d/0DPOIaItl/8BWKyCuAHMss47j0wlGbBSHdJIiS55akMvnAG0M
39y22Qqfzh1at9kBFeYeFIIU82ZLF3xOcE3z6pJZ4Dyvx4BYdXH77odo9uVK9s1l
3T3BlMcqd1hvZLMS7dviyH79jZo4CXSHiKzc7pQ2YfK5eKxKqONeXuECgYEAyXlG
vonaus/YTb1IBei9HwaccnQ/1HRn6MvfDjb7JJDIBhNClGPt6xRlzBbSZ73c2QEC
6Fu9h36K/HZ2qcLd2bXiNyhIV7b6tVKk+0Psoj0dL9EbhsD1OsmE1nTPyAc9XZbb
OPYxy+dpBCUA8/1U9+uiFoCa7mIbWcSQ+39gHuECgYAz82pQfct30aH4JiBrkNqP
nJfRq05UY70uk5k1u0ikLTRoVS/hJu/d4E1Kv4hBMqYCavFSwAwnvHUo51lVCr/y
xQOVYlsgnwBg2MX4+GjmIkqpSVCC8D7j/73MaWb746OIYZervQ8dbKahi2HbpsiG
8AHcVSA/agxZr38qvWV54QKBgCD5TlDE8x18AuTGQ9FjxAAd7uD0kbXNz2vUYg9L
hFL5tyL3aAAtUrUUw4xhd9IuysRhW/53dU+FsG2dXdJu6CxHjlyEpUJl2iZu/j15
YnMzGWHIEX8+eWRDsw/+Ujtko/B7TinGcWPz3cYl4EAOiCeDUyXnqnO1btCEUU44
DJ1BAoGBAJuPD27ErTSVtId90+M4zFPNibFP50KprVdc8CR37BE7r8vuGgNYXmnI
RLnGP9p3pVgFCktORuYS2J/6t84I3+A17nEoB4xvhTLeAinAW/uTQOUmNicOP4Ek
2MsLL2kHgL8bLTmvXV4FX+PXphrDKg1XxzOYn0otuoqdAQrkK4og
-----END RSA PRIVATE KEY-----
EOD,
];
$payload = [
'user_id' => '1',
];
return $manager->issue($payload, DAY);
}
}