Skip to content

Support Cached key set #535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
"require-dev": {
"cakephp/cakephp": "^4.0",
"cakephp/cakephp-codesniffer": "^4.0",
"firebase/php-jwt": "^5.5",
"phpunit/phpunit": "^8.5 || ^9.3"
"firebase/php-jwt": "^6.2",
"phpunit/phpunit": "^8.5 || ^9.3",
"symfony/cache": "^5.4 || ^6.0"
},
"suggest": {
"cakephp/orm": "To use \"OrmResolver\" (Not needed separately if using full CakePHP framework).",
Expand Down
8 changes: 4 additions & 4 deletions docs/en/authenticators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ example.
If provided will be used instead of the secret key.

You need to add the lib `firebase/php-jwt <https://github.com/firebase/php-jwt>`_
^5.5 to your app to use the ``JwtAuthenticator`` (v6.0 is not currently supported).
v6.2 or above to your app to use the ``JwtAuthenticator``.

By default the ``JwtAuthenticator`` uses ``HS256`` symmetric key algorithm and uses
the value of ``Cake\Utility\Security::salt()`` as encryption key.
Expand Down Expand Up @@ -310,9 +310,9 @@ Configuration options:
``null`` and all pages will be checked.
- **passwordHasher**: Password hasher to use for token hashing. Default
is ``DefaultPasswordHasher::class``.
- **salt**: When ``false`` no salt is used. When a string is passed that value is used as a salt value.
When ``true`` the default Security.salt is used. Default is ``true``. When a salt is used, the cookie value
will contain `hash(username + password + hmac(username + password, salt))`. This helps harden tokens against possible
- **salt**: When ``false`` no salt is used. When a string is passed that value is used as a salt value.
When ``true`` the default Security.salt is used. Default is ``true``. When a salt is used, the cookie value
will contain `hash(username + password + hmac(username + password, salt))`. This helps harden tokens against possible
database leaks and enables cookie values to be invalidated by rotating the salt value.

Usage
Expand Down
5 changes: 2 additions & 3 deletions docs/es/authenticators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ ejemplo.
- **queryParam**: Parámetro de la query para verificar el token. Por defecto
es ``token``.
- **tokenPrefix**: Prefijo del token. Por defecto es ``bearer``.
- **algorithms**: Array de algoritmos hashing para Firebase JWT.
El array por defecto es ``['HS256']``.
- **algorithm**: El algoritmo de hash para Firebase JWT. Por defecto es ``'HS256'``.
- **returnPayload**: Retornar o no el payload del token directamente
sin pasar a través de los identificadores. Por defecto es ``true``.
- **secretKey**: Por defecto es ``null`` pero será **requerido** pasar una
Expand Down Expand Up @@ -137,7 +136,7 @@ Agregue lo siguiente a su clase ``Application``::
$service->loadIdentifier('Authentication.JwtSubject');
$service->loadAuthenticator('Authentication.Jwt', [
'secretKey' => file_get_contents(CONFIG . '/jwt.pem'),
'algorithms' => ['RS256'],
'algorithm' => 'RS256',
'returnPayload' => false
]);
}
Expand Down
8 changes: 4 additions & 4 deletions docs/fr/authenticators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ source de données, par exemple.
d'accès. La valeur par défaut est ``token``.
- **tokenPrefix**: Le préfixe du jeton d'accès. La valeur par défaut est
``bearer``.
- **algorithms**: L'algorithme de hachage pour Firebase JWT. La valeur par
défaut est ``['HS256']``.
- **algorithm**: L'algorithme de hachage pour Firebase JWT. La valeur par défaut
est ``'HS256'``.
- **returnPayload**: Renvoyer ou non la payload du jeton d'accès directement
sans passer par les identificateurs. La valeur par défaut est ``true``.
- **secretKey**: La valeur par défaut est ``null`` mais vous **devez
Expand All @@ -122,7 +122,7 @@ source de données, par exemple.
S'il est fourni, il sera utilisé à la place de ``secret key``.

Pour utiliser le ``JwtAuthenticator``, vous devez ajouter à votre application la
bibliothèque `firebase/php-jwt <https://github.com/firebase/php-jwt>`__ v5.5 ou
bibliothèque `firebase/php-jwt <https://github.com/firebase/php-jwt>`__ v6.2 ou
supérieure.

Par défaut, le ``JwtAuthenticator`` utilise l'algorithme de clé symétrique
Expand Down Expand Up @@ -155,7 +155,7 @@ Ajoutez ce qui suit dans votre classe ``Application``::
$service->loadIdentifier('Authentication.JwtSubject');
$service->loadAuthenticator('Authentication.Jwt', [
'secretKey' => file_get_contents(CONFIG . '/jwt.pem'),
'algorithms' => 'RS256',
'algorithm' => 'RS256',
'returnPayload' => false
]);
}
Expand Down
4 changes: 2 additions & 2 deletions docs/ja/authenticators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ JWT 認証機能は、ヘッダーまたはクエリパラメータから `JWT t
- **header**: トークンを確認するためのヘッダー行です。デフォルトは ``Authorization`` です。
- **queryParam**: トークンをチェックするクエリパラメータ。デフォルトは ``token`` です。
- **tokenPrefix**: prefixトークン. デフォルトは ``bearer`` です。
- **algorithms**: Firebase JWT用のハッシュアルゴリズムの配列。デフォルトは配列 ``['HS256']`` です。
- **algorithm**: Firebase JWT のハッシュアルゴリズム。デフォルトは ``'HS256'`` です。
- **returnPayload**: 識別子を経由せずに、トークンのペイロードを直接返すか返さないか。デフォルトは ``true`` です。
- **secretKey**: デフォルトは ``null`` ですが、秘密鍵を ``Security::salt()`` 提供しているCakePHPアプリケーションのコンテキストではない場合は **必須** です。

Expand Down Expand Up @@ -109,7 +109,7 @@ JWT 認証機能は、ヘッダーまたはクエリパラメータから `JWT t
$service->loadIdentifier('Authentication.JwtSubject');
$service->loadAuthenticator('Authentication.Jwt', [
'secretKey' => file_get_contents(CONFIG . '/jwt.pem'),
'algorithms' => ['RS256'],
'algorithm' => 'RS256',
'returnPayload' => false
]);
}
Expand Down
49 changes: 18 additions & 31 deletions src/Authenticator/JwtAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@

use ArrayObject;
use Authentication\Identifier\IdentifierInterface;
use Cake\Utility\Hash;
use Cake\Cache\Cache;
use Cake\Http\Client;
use Cake\Utility\Security;
use Exception;
use Firebase\JWT\CachedKeySet;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Laminas\Diactoros\RequestFactory;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
use stdClass;
use Symfony\Component\Cache\Adapter\Psr16Adapter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this Symfony adapter? Cake's cache engine are already PSR16 compliant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, php-jwt opted for PSR-6 based Cache. Psr16Adapter returns a PSR6 Cache.
See https://symfony.com/doc/current/components/cache/psr6_psr16_adapters.html#using-a-psr-16-cache-object-as-a-psr-6-cache

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we write our own adapter instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cake cache adapters already implement the PSR16 interfaces. We could implement a 6 to 16 adapter in cake to avoid adding another medium sized dependency.


class JwtAuthenticator extends TokenAuthenticator
{
Expand All @@ -42,6 +46,7 @@ class JwtAuthenticator extends TokenAuthenticator
'secretKey' => null,
'subjectKey' => IdentifierInterface::CREDENTIAL_JWT_SUBJECT,
'jwks' => null,
'jwksCache' => null,
];

/**
Expand All @@ -58,24 +63,12 @@ public function __construct(IdentifierInterface $identifier, array $config = [])
{
parent::__construct($identifier, $config);

if (isset($config['algorithms'])) {
$this->setConfig('algorithms', $config['algorithms'], false);
}

if (empty($this->_config['secretKey'])) {
if (!class_exists(Security::class)) {
throw new RuntimeException('You must set the `secretKey` config key for JWT authentication.');
}
$this->setConfig('secretKey', \Cake\Utility\Security::getSalt());
}

if (isset($config['algorithms'])) {
deprecationWarning(
'The `algorithms` array config is deprecated, use the `algorithm` string config instead.'
. ' This is due to the new recommended usage of `firebase/php-jwt`.'
. 'See https://github.com/firebase/php-jwt/releases/tag/v5.5.0'
);
}
}

/**
Expand Down Expand Up @@ -160,26 +153,20 @@ public function getPayload(?ServerRequestInterface $request = null): ?object
*/
protected function decodeToken(string $token): ?object
{
$algorithms = $this->getConfig('algorithms');
if ($algorithms) {
return JWT::decode(
$token,
$this->getConfig('secretKey'),
$algorithms
);
}

$jsonWebKeySet = $this->getConfig('jwks');
if ($jsonWebKeySet) {
$keySet = JWK::parseKeySet($jsonWebKeySet);
/*
* TODO Converting Keys to Key Objects is no longer needed in firebase/php-jwt ^6.0
* @link https://github.com/firebase/php-jwt/pull/376/files#diff-374f5998b3c572d86be0e79432aac3de362c79e8fb146b9ce422dc2388cdc5daR50
*/
$keyAlgorithms = Hash::combine($jsonWebKeySet['keys'], '{n}.kid', '{n}.alg');
array_walk($keySet, function (&$keyMaterial, $k) use ($keyAlgorithms) {
$keyMaterial = new Key($keyMaterial, $keyAlgorithms[$k]);
});
$jsonWebKeySetCache = $this->getConfig('jwksCache');
if ($jsonWebKeySetCache) {
$keySet = new CachedKeySet(
$jsonWebKeySet,
new Client(),
new RequestFactory(),
new Psr16Adapter(Cache::pool($jsonWebKeySetCache)),
3000
);
} else {
$keySet = JWK::parseKeySet($jsonWebKeySet);
}

return JWT::decode(
$token,
Expand Down
38 changes: 0 additions & 38 deletions tests/TestCase/Authenticator/JwtAuthenticatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,6 @@ public function setUp(): void
$this->identifiers = new IdentifierCollection([]);
}

/**
* Test that "algorithms" config overwrites the default value instead of merging.
*
* @deprecated
* @return void
*/
public function testAlgorithmsOverwrite()
{
$this->deprecated(function () {
$authenticator = new JwtAuthenticator($this->identifiers, [
'algorithms' => ['RS256'],
]);

$this->assertSame(['RS256'], $authenticator->getConfig('algorithms'));
});
}

/**
* testAuthenticateViaHeaderToken
*
Expand All @@ -121,27 +104,6 @@ public function testAuthenticateViaHeaderToken()
$this->assertInstanceOf(ArrayAccess::class, $result->getData());
}

/**
* @deprecated
*/
public function testUsingDeprecatedConfig()
{
$this->request = ServerRequestFactory::fromGlobals(
['REQUEST_URI' => '/']
);
$this->request = $this->request->withAddedHeader('Authorization', 'Bearer ' . $this->tokenHS256);

$this->deprecated(function () {
$authenticator = new JwtAuthenticator($this->identifiers, [
'secretKey' => 'secretKey',
'subjectKey' => 'subjectId',
'algorithms' => ['HS256'],
]);
// Appease lowest build
$this->assertNotEmpty($authenticator);
});
}

/**
* testAuthenticateViaQueryParamToken
*
Expand Down