Skip to content

Commit 81a92a6

Browse files
author
Bizley
authored
Merge pull request #51 from bizley/v4
v4
2 parents c764936 + 3da68b1 commit 81a92a6

File tree

11 files changed

+290
-321
lines changed

11 files changed

+290
-321
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
strategy:
5050
fail-fast: false
5151
matrix:
52-
php: ['7.4', '8.0', '8.1']
52+
php: ['8.1', '8.2']
5353

5454
steps:
5555
- name: Checkout

INSTRUCTION.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ Validation constraints used here are:
6666
- `LooseValidAt` - this will make sure that token is not expired yet allowing 10 seconds leeway (in case of some delays
6767
between the server and the client), we are here also setting the same time zone that is used in the application.
6868

69+
*NOTE*: The above implementation requires to install `lcobucci/clock` library first (run `composer req lcobucci/clock`).
70+
If you prefer other PSR-20 clock implementation you must change the above `\Lcobucci\Clock\SystemClock()` usage.
71+
6972
You can also add here any other constraint that you find necessary. The available list is at
7073
https://github.com/lcobucci/jwt/tree/4.1.x/src/Validation/Constraint, and you can always write your own constraint as
7174
long as it implements `Lcobucci\JWT\Validation\Constraint`.

README.md

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@ This extension provides the [JWT](https://github.com/lcobucci/jwt) integration f
99

1010
> This is a fork of [sizeg/yii2-jwt](https://github.com/sizeg/yii2-jwt) package
1111
12-
**Version 3.x of this package uses `lcobucci/jwt` [v4](https://github.com/lcobucci/jwt/releases/tag/4.0.0)
13-
and introduces critical BC changes, [see v4 lcobucci/jwt Upgrade Guide](https://lcobucci-jwt.readthedocs.io/en/latest/upgrading/).
14-
For 2.x (and `lcobucci/jwt` v3) install `^2.0`.**
12+
# Available versions
13+
14+
| bizley/yii2-jwt | lcobucci/jwt | php |
15+
|:---------------:|:------------:|:-------:|
16+
| `^4.0` | `^5.0` | `>=8.1` |
17+
| `^3.0` | `^4.0` | `>=7.4` |
18+
| `^2.0` | `^3.0` | `>=7.1` |
19+
20+
See [lcobucci/jwt](https://github.com/lcobucci/jwt) repo for details about the version.
1521

1622
## Installation
1723

@@ -20,12 +26,12 @@ Add the package to your `composer.json`:
2026
```json
2127
{
2228
"require": {
23-
"bizley/jwt": "^3.0"
29+
"bizley/jwt": "^4.0"
2430
}
2531
}
2632
```
2733

28-
and run `composer update` or alternatively run `composer require bizley/jwt:^3.0`
34+
and run `composer update` or alternatively run `composer require bizley/jwt:^4.0`
2935

3036
## Basic usage
3137

@@ -68,17 +74,7 @@ and `algorithmTypes` and using its ID for `signer`.
6874
### Note on signers and minimum bits requirement
6975

7076
Since `lcobucci/jwt 4.2.0` signers require the minimum key length to make sure those are properly secured, otherwise
71-
the `InvalidKeyProvided` is thrown. If for any reason (**and on your own risk**) you would still like to use the less
72-
secure key (for example HS256 with fewer than 256 bits length) you can wire it through this library by using the
73-
`Unsafe` version of that signer (for example `Lcobucci\JWT\Signer\Hmac\Sha256` has the unsafe version
74-
`Lcobucci\JWT\Signer\Hmac\UnsafeSha256`). Unsafe versions are using the same algorithm ID, so you don't have to add them
75-
on the `Jwt::$algorithmTypes` list, but you need to configure them manually for your signer configuration like:
76-
77-
```php
78-
[
79-
'signer' => [\Lcobucci\JWT\Signer\Hmac\UnsafeSha256::class],
80-
]
81-
```
77+
the `InvalidKeyProvided` is thrown.
8278

8379
### Keys
8480

@@ -91,16 +87,12 @@ Configuration array can be as the following:
9187
[
9288
'key' => /* key content */,
9389
'passphrase' => /* key passphrase */,
94-
'store' => /* storage type */,
9590
'method' => /* method type */,
9691
]
9792
```
9893

9994
- key (Jwt::KEY) - _string_, default `''`,
10095
- passphrase (Jwt::PASSPHRASE) - _string_, default `''`,
101-
- store (Jwt::STORE) - _string_, default `Jwt::STORE_IN_MEMORY`,
102-
available: `Jwt::STORE_IN_MEMORY`, `Jwt::STORE_LOCAL_FILE_REFERENCE` (deprecated since 3.2.0, will be removed in 4.0.0)
103-
(see https://lcobucci-jwt.readthedocs.io/en/latest/configuration/)
10496
- method (Jwt::METHOD) - _string_, default `Jwt::METHOD_PLAIN`,
10597
available: `Jwt::METHOD_PLAIN`, `Jwt::METHOD_BASE64`, `Jwt::METHOD_FILE`
10698
(see https://lcobucci-jwt.readthedocs.io/en/latest/configuration/)
@@ -111,18 +103,16 @@ Simple string keys are shortcuts to the following array configs:
111103
[
112104
'key' => /* given key itself */,
113105
'passphrase' => '',
114-
'store' => Jwt::STORE_IN_MEMORY,
115106
'method' => Jwt::METHOD_FILE,
116107
]
117108
```
118-
Detecting `@` at the beginning assumes Yii alias has been provided so it will be resolved with `Yii::getAlias()`.
109+
Detecting `@` at the beginning assumes Yii alias has been provided, so it will be resolved with `Yii::getAlias()`.
119110

120111
- key doesn't start with `@` nor `file://`:
121112
```php
122113
[
123114
'key' => /* given key itself */,
124115
'passphrase' => '',
125-
'store' => Jwt::STORE_IN_MEMORY,
126116
'method' => Jwt::METHOD_PLAIN,
127117
]
128118
```

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
"source": "https://github.com/bizley/yii2-jwt"
1919
},
2020
"require": {
21-
"php": ">=7.4",
22-
"lcobucci/jwt": "^4.1",
21+
"php": ">=8.1",
22+
"lcobucci/jwt": "^5.0",
2323
"yiisoft/yii2": ">=2.0.14 <2.1"
2424
},
2525
"require-dev": {
2626
"infection/infection": "*",
27+
"lcobucci/clock": "^3.0",
2728
"phpstan/phpstan": "*",
28-
"phpunit/phpunit": "^9.3",
29+
"phpunit/phpunit": "^9.6",
2930
"roave/security-advisories": "dev-latest"
3031
},
3132
"autoload": {

infection.json.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@default": true,
1515
"MethodCallRemoval": {
1616
"ignore": [
17-
"bizley\\jwt\\Jwt::init::193",
17+
"bizley\\jwt\\Jwt::init::186",
1818
"bizley\\jwt\\JwtHttpBearerAuth::init::77"
1919
]
2020
}

src/Jwt.php

Lines changed: 42 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Lcobucci\JWT\Decoder;
1111
use Lcobucci\JWT\Encoder;
1212
use Lcobucci\JWT\Encoding\CannotDecodeContent;
13+
use Lcobucci\JWT\Encoding\JoseEncoder;
1314
use Lcobucci\JWT\Parser;
1415
use Lcobucci\JWT\Signer;
1516
use Lcobucci\JWT\Token;
@@ -26,10 +27,10 @@
2627
use function is_callable;
2728
use function is_string;
2829
use function reset;
29-
use function strpos;
30+
use function str_starts_with;
3031

3132
/**
32-
* JSON Web Token implementation based on lcobucci/jwt library v4.
33+
* JSON Web Token implementation based on lcobucci/jwt library v5.
3334
* @see https://github.com/lcobucci/jwt
3435
*
3536
* @author Paweł Bizley Brzozowski <[email protected]> since 2.0 (fork)
@@ -50,7 +51,6 @@ class Jwt extends Component
5051
public const BLAKE2B = 'BLAKE2B';
5152

5253
public const STORE_IN_MEMORY = 'in_memory';
53-
public const STORE_LOCAL_FILE_REFERENCE = 'local_file_reference'; // deprecated since 3.2.0, will be removed in 4.0.0
5454

5555
public const METHOD_PLAIN = 'plain';
5656
public const METHOD_BASE64 = 'base64';
@@ -69,26 +69,20 @@ class Jwt extends Component
6969
* This can be a simple string, an instance of Key, or a configuration array.
7070
* The configuration takes the following array keys:
7171
* - 'key' => Key's value or path to the key file.
72-
* - 'store' => Either `Jwt::STORE_IN_MEMORY` or `Jwt::STORE_LOCAL_FILE_REFERENCE` (deprecated) -
73-
* whether to keep the key in the memory or as a reference to a local file.
7472
* - 'method' => `Jwt::METHOD_PLAIN`, `Jwt::METHOD_BASE64`, or `Jwt::METHOD_FILE` - whether the key is a plain
7573
* text, base64 encoded text, or a file.
76-
* In case the 'store' is set to `Jwt::STORE_LOCAL_FILE_REFERENCE` (deprecated), only
77-
* `Jwt::METHOD_FILE` method is available.
7874
* - 'passphrase' => Key's passphrase.
7975
* In case a simple string is provided (and it does not start with 'file://' or '@') the following configuration
8076
* is assumed:
8177
* [
8278
* 'key' => // the original given value,
83-
* 'store' => Jwt::STORE_IN_MEMORY,
8479
* 'method' => Jwt::METHOD_PLAIN,
8580
* 'passphrase' => '',
8681
* ]
8782
* In case a simple string is provided and it does start with 'file://' (direct file path) or '@' (Yii alias)
8883
* the following configuration is assumed:
8984
* [
9085
* 'key' => // the original given value,
91-
* 'store' => Jwt::STORE_IN_MEMORY,
9286
* 'method' => Jwt::METHOD_FILE,
9387
* 'passphrase' => '',
9488
* ]
@@ -107,12 +101,11 @@ class Jwt extends Component
107101
public $verifyingKey = '';
108102

109103
/**
110-
* @var string|Signer|null Signer ID or Signer instance to be used for signing/verifying.
111-
* See $signers for available values. In case it's not set, no algorithm will be used, which may be handy if you
112-
* want to do some testing, but it's NOT recommended for production environments.
104+
* @var string|Signer Signer ID or Signer instance to be used for signing/verifying.
105+
* See $signers for available values. Since 4.0.0 it cannot be empty anymore.
113106
* @since 3.0.0
114107
*/
115-
public $signer;
108+
public $signer = '';
116109

117110
/**
118111
* @var array<string, string[]> Default signers configuration. When instantiated it will use selected array to
@@ -192,31 +185,27 @@ public function init(): void
192185
{
193186
parent::init();
194187

195-
if ($this->signer === null) {
196-
$this->configuration = Configuration::forUnsecuredSigner($this->prepareEncoder(), $this->prepareDecoder());
188+
$signerId = $this->signer;
189+
if ($this->signer instanceof Signer) {
190+
$signerId = $this->signer->algorithmId();
191+
}
192+
if (in_array($signerId, $this->algorithmTypes[self::SYMMETRIC], true)) {
193+
$this->configuration = Configuration::forSymmetricSigner(
194+
$this->prepareSigner($this->signer),
195+
$this->prepareKey($this->signingKey),
196+
$this->prepareEncoder(),
197+
$this->prepareDecoder()
198+
);
199+
} elseif (in_array($signerId, $this->algorithmTypes[self::ASYMMETRIC], true)) {
200+
$this->configuration = Configuration::forAsymmetricSigner(
201+
$this->prepareSigner($this->signer),
202+
$this->prepareKey($this->signingKey),
203+
$this->prepareKey($this->verifyingKey),
204+
$this->prepareEncoder(),
205+
$this->prepareDecoder()
206+
);
197207
} else {
198-
$signerId = $this->signer;
199-
if ($this->signer instanceof Signer) {
200-
$signerId = $this->signer->algorithmId();
201-
}
202-
if (in_array($signerId, $this->algorithmTypes[self::SYMMETRIC], true)) {
203-
$this->configuration = Configuration::forSymmetricSigner(
204-
$this->prepareSigner($this->signer),
205-
$this->prepareKey($this->signingKey),
206-
$this->prepareEncoder(),
207-
$this->prepareDecoder()
208-
);
209-
} elseif (in_array($signerId, $this->algorithmTypes[self::ASYMMETRIC], true)) {
210-
$this->configuration = Configuration::forAsymmetricSigner(
211-
$this->prepareSigner($this->signer),
212-
$this->prepareKey($this->signingKey),
213-
$this->prepareKey($this->verifyingKey),
214-
$this->prepareEncoder(),
215-
$this->prepareDecoder()
216-
);
217-
} else {
218-
throw new InvalidConfigException('Invalid signer ID!');
219-
}
208+
throw new InvalidConfigException('Invalid signer ID!');
220209
}
221210
}
222211

@@ -251,6 +240,7 @@ public function getConfiguration(): Configuration
251240

252241
/**
253242
* Since 3.0.0 this method is using different signature.
243+
* Please note that since 4.0.0 Builder object is immutable.
254244
* @see https://lcobucci-jwt.readthedocs.io/en/latest/issuing-tokens/ for details of using the builder.
255245
* @throws InvalidConfigException
256246
*/
@@ -270,6 +260,7 @@ public function getParser(): Parser
270260
}
271261

272262
/**
263+
* @param non-empty-string $jwt
273264
* @throws CannotDecodeContent When something goes wrong while decoding.
274265
* @throws Token\InvalidTokenStructure When token string structure is invalid.
275266
* @throws Token\UnsupportedHeaderFound When parsed token has an unsupported header.
@@ -284,7 +275,7 @@ public function parse(string $jwt): Token
284275
/**
285276
* This method goes through every single constraint in the set, groups all the violations, and throws an exception
286277
* with the grouped violations.
287-
* @param string|Token $jwt JWT string or instance of Token
278+
* @param non-empty-string|Token $jwt JWT string or instance of Token
288279
* @throws Validation\RequiredConstraintsViolated When constraint is violated
289280
* @throws Validation\NoConstraintsGiven When no constraints are provided
290281
* @throws InvalidConfigException
@@ -300,7 +291,7 @@ public function assert($jwt): void
300291

301292
/**
302293
* This method return false on first constraint violation
303-
* @param string|Token $jwt JWT string or instance of Token
294+
* @param non-empty-string|Token $jwt JWT string or instance of Token
304295
* @throws InvalidConfigException
305296
* @since 3.0.0
306297
*/
@@ -331,22 +322,19 @@ private function prepareKey($key): Signer\Key
331322
if ($key === '') {
332323
throw new InvalidConfigException('Empty string used as a key configuration!');
333324
}
334-
if (strpos($key, '@') === 0) {
325+
if (str_starts_with($key, '@')) {
335326
$keyConfig = [
336327
self::KEY => Yii::getAlias($key),
337-
self::STORE => self::STORE_IN_MEMORY,
338328
self::METHOD => self::METHOD_FILE,
339329
];
340-
} elseif (strpos($key, 'file://') === 0) {
330+
} elseif (str_starts_with($key, 'file://')) {
341331
$keyConfig = [
342332
self::KEY => $key,
343-
self::STORE => self::STORE_IN_MEMORY,
344333
self::METHOD => self::METHOD_FILE,
345334
];
346335
} else {
347336
$keyConfig = [
348337
self::KEY => $key,
349-
self::STORE => self::STORE_IN_MEMORY,
350338
self::METHOD => self::METHOD_PLAIN,
351339
];
352340
}
@@ -357,42 +345,27 @@ private function prepareKey($key): Signer\Key
357345
}
358346

359347
$value = $keyConfig[self::KEY] ?? '';
360-
$store = $keyConfig[self::STORE] ?? self::STORE_IN_MEMORY;
361348
$method = $keyConfig[self::METHOD] ?? self::METHOD_PLAIN;
362349
$passphrase = $keyConfig[self::PASSPHRASE] ?? '';
363350

364-
if (!is_string($value)) {
351+
if (!is_string($value) || $value === '') {
365352
throw new InvalidConfigException('Invalid key value!');
366353
}
367-
if (!in_array($store, [self::STORE_IN_MEMORY, self::STORE_LOCAL_FILE_REFERENCE], true)) {
368-
throw new InvalidConfigException('Invalid key store!');
369-
}
370354
if (!in_array($method, [self::METHOD_PLAIN, self::METHOD_BASE64, self::METHOD_FILE], true)) {
371355
throw new InvalidConfigException('Invalid key method!');
372356
}
373357
if (!is_string($passphrase)) {
374358
throw new InvalidConfigException('Invalid key passphrase!');
375359
}
376360

377-
if ($store === self::STORE_IN_MEMORY) {
378-
if ($value === '') {
379-
return Signer\Key\InMemory::empty();
380-
}
381-
if ($method === self::METHOD_BASE64) {
382-
return Signer\Key\InMemory::base64Encoded($value, $passphrase);
383-
}
384-
if ($method === self::METHOD_FILE) {
385-
return Signer\Key\InMemory::file($value, $passphrase);
386-
}
387-
388-
return Signer\Key\InMemory::plainText($value, $passphrase);
361+
if ($method === self::METHOD_BASE64) {
362+
return Signer\Key\InMemory::base64Encoded($value, $passphrase);
389363
}
390-
391-
if ($method !== self::METHOD_FILE) {
392-
throw new InvalidConfigException('Invalid key store and method combination!');
364+
if ($method === self::METHOD_FILE) {
365+
return Signer\Key\InMemory::file($value, $passphrase);
393366
}
394367

395-
return Signer\Key\InMemory::file($value, $passphrase);
368+
return Signer\Key\InMemory::plainText($value, $passphrase);
396369
}
397370

398371
/**
@@ -454,10 +427,10 @@ private function prepareValidationConstraints(): array
454427
/**
455428
* @throws InvalidConfigException
456429
*/
457-
private function prepareEncoder(): ?Encoder
430+
private function prepareEncoder(): Encoder
458431
{
459432
if ($this->encoder === null) {
460-
return null;
433+
return new JoseEncoder();
461434
}
462435

463436
/** @var Encoder $encoder */
@@ -469,10 +442,10 @@ private function prepareEncoder(): ?Encoder
469442
/**
470443
* @throws InvalidConfigException
471444
*/
472-
private function prepareDecoder(): ?Decoder
445+
private function prepareDecoder(): Decoder
473446
{
474447
if ($this->decoder === null) {
475-
return null;
448+
return new JoseEncoder();
476449
}
477450

478451
/** @var Decoder $decoder */

0 commit comments

Comments
 (0)