Skip to content

Commit

Permalink
Possibility of mitigation of security issue CVE-2021-46743 for v1 branch
Browse files Browse the repository at this point in the history
  • Loading branch information
dakujem committed Apr 19, 2023
1 parent 9d27223 commit 6b47d8c
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 56 deletions.
14 changes: 14 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ Auth-middleware follows semantic versioning.\
Any issues should be reported.


## v1.2

Provides means for mitigation of security vulnerability **CVE-2021-46743** by using the new `Secret` configuration object.
The peer library for handling tokens `firebase/php-jwt` must be upgraded to v5.5 in order to do so.

1. use a single secret+algorithm combination
- either using the `Secret` object instead of string constants when using `AuthWizard` or `AuthFactory`
- or passing an array with a single algorithm to the `$algos` parameter of `FirebaseJwtDecoder` constructor when using the decoder as standalone
2. use multiple `Secret` objects and pass them to the `$secret` parameter AND use "kid" JWT header parameter when encoding the JWT
- the JWT encoding must also factor-in the `kid` parameter when using multiple possible secret+algorithm combinations

For more information, see https://github.com/firebase/php-jwt/issues/351.


## v1.1

Improved default injector:
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
},
"require-dev": {
"ext-json": "*",
"firebase/php-jwt": "^5",
"nette/tester": "^2.4.1",
"tracy/tracy": "^2.3",
"firebase/php-jwt": "^5.0",
"slim/psr7": "^1.2",
"slim/slim": "^4.5",
"slim/psr7": "^1.2"
"tracy/tracy": "^2.3"
},
"autoload": {
"psr-4": {
Expand Down
29 changes: 24 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Use `Dakujem\Middleware\AuthWizard` for convenience:
```php
/* @var Slim\App $app */
$app->add(AuthWizard::assertTokens($app->getResponseFactory()));
$app->add(AuthWizard::decodeTokens('a-secret-api-key-never-to-commit'));
$app->add(AuthWizard::decodeTokens(new Secret('a-secret-api-key-never-to-commit', 'HS256')));
```

The pair of middleware (MW) will look for a [JWT](https://jwt.io/introduction/)
Expand All @@ -41,7 +41,7 @@ $decodedToken = $request->getAttribute('token');

You can choose to apply the assertion to selected routes only instead of every route:
```php
$mwFactory = AuthWizard::factory('a-secret-api-key-never-to-commit', $app->getResponseFactory());
$mwFactory = AuthWizard::factory(new Secret('a-secret-api-key-never-to-commit', 'HS256'), $app->getResponseFactory());

// Decode the token for all routes,
$app->add($mwFactory->decodeTokens());
Expand Down Expand Up @@ -74,8 +74,9 @@ read the ["Compose Your Own Middleware"](#compose-your-own-middleware) chapter b
## Extracting & Decoding JWT

```php
AuthWizard::decodeTokens(
'a-secret-api-key-never-to-commit',
AuthWizard::decodeTokens(__
// a combination of secret and the encryption algorithm used
new Secret('a-secret-api-key-never-to-commit', 'HS256'),
'token', // what attribute to put the decoded token to
'Authorization', // what header to look for the Bearer token in
'token', // what cookie to look for the raw token in
Expand Down Expand Up @@ -252,7 +253,7 @@ It is used as a _decoder_ for the `TokenMiddleware`.\
You can swap it for a different implementation.

You need to install [Firebase JWT](https://github.com/firebase/php-jwt) package in order to use this decoder.\
`composer require firebase/php-jwt:"^5.0"`
`composer require firebase/php-jwt:"^5.5"`


### Logger
Expand All @@ -275,6 +276,24 @@ Run unit tests using the following command:
`$` `composer test`


## Compatibility

| `dakujem/auth-middleware` | PHP |
|:--------------------------|:----------|
| `1.x` | 7.4 - 8.2 |
| `2.x` | 8+ |


In order to use the `FirebaseJwtDecoder` decoder, a correct version of `firebase/php-jwt` must be installed.
The use of this decoder is not required though.

| `dakujem/auth-middleware` | `firebase/php-jwt` |
|:--------------------------|:-----------------------------------------------------------|
| `1.0` - `1.2` | `^5` |
| `1.2` | `^6` when using a single secret+algorithm combination only |
| `2` | `^5.5`, `^6` and above |


## Contributing

Ideas, feature requests and other contribution is welcome.
Expand Down
22 changes: 11 additions & 11 deletions src/Factory/AuthFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

use Dakujem\Middleware\FirebaseJwtDecoder;
use Dakujem\Middleware\GenericMiddleware;
use Dakujem\Middleware\SecretContract;
use Dakujem\Middleware\TokenManipulators as Man;
use Dakujem\Middleware\TokenMiddleware;
use Firebase\JWT\JWT;
use LogicException;
use Psr\Http\Message\ResponseFactoryInterface as ResponseFactory;
use Psr\Http\Message\ResponseInterface as Response;
Expand Down Expand Up @@ -167,20 +167,20 @@ public function inspectTokens(
}

/**
* @deprecated please use AuthWizard::defaultDecoder instead
*
* Creates a default decoder factory.
* The factory can be used for the constructor.
*
* @param string $secret secret key for JWT decoder
* @param string|SecretContract[]|SecretContract $secret secret key for JWT decoder
* @param string|null $algo optional algorithm; only used when $secret is a string
* @return callable fn():FirebaseJwtDecoder
*/
public static function defaultDecoderFactory(string $secret): callable
{
if (!class_exists(JWT::class)) {
throw new LogicException(
'Firebase JWT is not installed. ' .
'Requires firebase/php-jwt package (`composer require firebase/php-jwt:"^5.0"`).'
);
}
return fn(): FirebaseJwtDecoder => new FirebaseJwtDecoder($secret);
public static function defaultDecoderFactory(
$secret,
?string $algo = null
): callable {
$decoder = AuthWizard::defaultDecoder($secret, $algo);
return fn(): FirebaseJwtDecoder => $decoder;
}
}
76 changes: 57 additions & 19 deletions src/Factory/AuthWizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@

namespace Dakujem\Middleware\Factory;

use Dakujem\Middleware\FirebaseJwtDecoder;
use Dakujem\Middleware\GenericMiddleware;
use Dakujem\Middleware\Secret;
use Dakujem\Middleware\SecretContract;
use Dakujem\Middleware\TokenManipulators as Man;
use Dakujem\Middleware\TokenMiddleware;
use Firebase\JWT\JWT;
use LogicException;
use Psr\Http\Message\ResponseFactoryInterface as ResponseFactory;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Log\LoggerInterface as Logger;
Expand All @@ -18,25 +23,12 @@
*/
final class AuthWizard
{
/**
* Create an instance of AuthFactory.
*
* @param string|null $secret
* @param ResponseFactory|null $responseFactory
* @return AuthFactory
*/
public static function factory(?string $secret, ?ResponseFactory $responseFactory): AuthFactory
{
return new AuthFactory(
$secret !== null ? AuthFactory::defaultDecoderFactory($secret) : null,
$responseFactory
);
}
public static string $defaultAlgo = 'HS256';

/**
* @see AuthFactory::decodeTokens()
*
* @param string $secret API secret key
* @param string|SecretContract[]|SecretContract $secret API secret key
* @param string|null $tokenAttribute
* @param string|null $headerName
* @param string|null $cookieName
Expand All @@ -45,14 +37,14 @@ public static function factory(?string $secret, ?ResponseFactory $responseFactor
* @return TokenMiddleware
*/
public static function decodeTokens(
string $secret,
$secret,
?string $tokenAttribute = null,
?string $headerName = Man::HEADER_NAME,
?string $cookieName = Man::COOKIE_NAME,
?string $errorAttribute = null,
?Logger $logger = null
): MiddlewareInterface {
return static::factory($secret, null)->decodeTokens(
return self::factory($secret, null)->decodeTokens(
$tokenAttribute,
$headerName,
$cookieName,
Expand All @@ -74,7 +66,7 @@ public static function assertTokens(
?string $tokenAttribute = null,
?string $errorAttribute = null
): MiddlewareInterface {
return static::factory(null, $responseFactory)->assertTokens($tokenAttribute, $errorAttribute);
return self::factory(null, $responseFactory)->assertTokens($tokenAttribute, $errorAttribute);
}

/**
Expand All @@ -92,6 +84,52 @@ public static function inspectTokens(
?string $tokenAttribute = null,
?string $errorAttribute = null
): MiddlewareInterface {
return static::factory(null, $responseFactory)->inspectTokens($inspector, $tokenAttribute, $errorAttribute);
return self::factory(null, $responseFactory)->inspectTokens($inspector, $tokenAttribute, $errorAttribute);
}

/**
* Create an instance of AuthFactory.
*
* @param string|SecretContract[]|SecretContract|null $secret
* @param ResponseFactory|null $responseFactory
* @return AuthFactory
*/
public static function factory(
$secret,
?ResponseFactory $responseFactory
): AuthFactory {
$decoder = $secret !== null ? self::defaultDecoder($secret) : null;
return new AuthFactory(
$decoder !== null ? fn() => $decoder : null,
$responseFactory
);
}

/**
* Creates a default decoder factory.
* The factory can be used for the constructor.
*
* @param string|SecretContract[]|SecretContract $secret secret key for JWT decoder
* @param string|null $algo optional algorithm; only used when $secret is a string
* @return callable fn():FirebaseJwtDecoder
* @throws
*/
public static function defaultDecoder(
$secret,
?string $algo = null
): callable {
if (!class_exists(JWT::class)) {
throw new LogicException(
'Firebase JWT is not installed. ' .
'Requires firebase/php-jwt package (`composer require firebase/php-jwt:"^5.5"`).'
);
}
if ($algo !== null && is_string($secret)) {
$secret = new Secret($secret, $algo);
}
return new FirebaseJwtDecoder(
$secret,
$algo,
);
}
}
Loading

0 comments on commit 6b47d8c

Please sign in to comment.