From cfc37d62048c8e45b6ba4fec02045edb8d9263e2 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 14 Jan 2026 02:38:11 +0100 Subject: [PATCH 1/3] Add default TokenIdentifier for PrimaryKeySessionAuthenticator PrimaryKeySessionAuthenticator now works out of the box without requiring explicit identifier configuration. When no identifier is provided, it lazily creates a TokenIdentifier configured to look up users by their `id` field. Before: ```php $service->loadAuthenticator('Authentication.PrimaryKeySession', [ 'identifier' => [ 'className' => 'Authentication.Token', 'tokenField' => 'id', 'dataField' => 'key', ], ]); ``` After: ```php $service->loadAuthenticator('Authentication.PrimaryKeySession'); ``` Custom configuration is still supported by passing an explicit identifier or by using the `idField` and `identifierKey` config options which propagate to the default TokenIdentifier. --- .../PrimaryKeySessionAuthenticator.php | 81 +++++++++++++++++-- .../PrimaryKeySessionAuthenticatorTest.php | 59 +++++++++++++- 2 files changed, 132 insertions(+), 8 deletions(-) diff --git a/src/Authenticator/PrimaryKeySessionAuthenticator.php b/src/Authenticator/PrimaryKeySessionAuthenticator.php index a55e8bb6..a03f896f 100644 --- a/src/Authenticator/PrimaryKeySessionAuthenticator.php +++ b/src/Authenticator/PrimaryKeySessionAuthenticator.php @@ -4,6 +4,7 @@ namespace Authentication\Authenticator; use ArrayAccess; +use Authentication\Identifier\IdentifierFactory; use Authentication\Identifier\IdentifierInterface; use Cake\Http\Exception\UnauthorizedException; use Psr\Http\Message\ResponseInterface; @@ -11,21 +12,87 @@ /** * Session Authenticator with only ID + * + * This authenticator stores only the user's primary key in the session, + * and looks up the full user record from the database on each request. + * + * By default, it uses a TokenIdentifier configured to look up users by + * their `id` field. This works out of the box for most applications: + * + * ```php + * $service->loadAuthenticator('Authentication.PrimaryKeySession'); + * ``` + * + * You can customize the identifier configuration if needed: + * + * ```php + * $service->loadAuthenticator('Authentication.PrimaryKeySession', [ + * 'identifier' => [ + * 'className' => 'Authentication.Token', + * 'tokenField' => 'uuid', + * 'dataField' => 'key', + * 'resolver' => [ + * 'className' => 'Authentication.Orm', + * 'userModel' => 'Members', + * ], + * ], + * ]); + * ``` */ class PrimaryKeySessionAuthenticator extends SessionAuthenticator { /** - * @param \Authentication\Identifier\IdentifierInterface|null $identifier - * @param array $config + * Default config for this object. + * + * - `identifierKey` The key used when passing the ID to the identifier. + * - `idField` The field on the user entity that contains the primary key. + * + * @var array + */ + protected array $_defaultConfig = [ + 'fields' => [], + 'sessionKey' => 'Auth', + 'impersonateSessionKey' => 'AuthImpersonate', + 'identify' => false, + 'identityAttribute' => 'identity', + 'identifierKey' => 'key', + 'idField' => 'id', + ]; + + /** + * Constructor + * + * Bypasses SessionAuthenticator's default PasswordIdentifier creation + * to allow lazy initialization of the TokenIdentifier in getIdentifier(). + * + * @param \Authentication\Identifier\IdentifierInterface|null $identifier Identifier instance. + * @param array $config Configuration settings. */ public function __construct(?IdentifierInterface $identifier, array $config = []) { - $config += [ - 'identifierKey' => 'key', - 'idField' => 'id', - ]; + $this->_identifier = $identifier; + $this->setConfig($config); + } + + /** + * Gets the identifier. + * + * If no identifier was explicitly configured, creates a default TokenIdentifier + * configured to look up users by their primary key (`id` field). + * + * @return \Authentication\Identifier\IdentifierInterface + */ + public function getIdentifier(): IdentifierInterface + { + if ($this->_identifier === null) { + $this->_identifier = IdentifierFactory::create([ + 'className' => 'Authentication.Token', + 'tokenField' => $this->getConfig('idField'), + 'dataField' => $this->getConfig('identifierKey'), + ]); + } - parent::__construct($identifier, $config); + return $this->_identifier; } /** diff --git a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php index 5b6d8806..7c5f05fd 100644 --- a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php @@ -67,7 +67,7 @@ public function setUp(): void } /** - * Test authentication + * Test authentication with explicit identifier * * @return void */ @@ -89,6 +89,63 @@ public function testAuthenticateSuccess() $this->assertSame(Result::SUCCESS, $result->getStatus()); } + /** + * Test authentication works with default identifier (no explicit configuration) + * + * @return void + */ + public function testAuthenticateSuccessWithDefaultIdentifier() + { + $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/']); + + $this->sessionMock->expects($this->once()) + ->method('read') + ->with('Auth') + ->willReturn(1); + + $request = $request->withAttribute('session', $this->sessionMock); + + // No identifier passed - should use the default TokenIdentifier + $authenticator = new PrimaryKeySessionAuthenticator(null); + $result = $authenticator->authenticate($request); + + $this->assertInstanceOf(Result::class, $result); + $this->assertSame(Result::SUCCESS, $result->getStatus()); + } + + /** + * Test getIdentifier returns default TokenIdentifier when none configured + * + * @return void + */ + public function testGetIdentifierReturnsDefaultWhenNotConfigured() + { + $authenticator = new PrimaryKeySessionAuthenticator(null); + $identifier = $authenticator->getIdentifier(); + + $this->assertInstanceOf(\Authentication\Identifier\TokenIdentifier::class, $identifier); + $this->assertSame('id', $identifier->getConfig('tokenField')); + $this->assertSame('key', $identifier->getConfig('dataField')); + } + + /** + * Test custom idField/identifierKey config propagates to default identifier + * + * @return void + */ + public function testGetIdentifierUsesCustomConfig() + { + $authenticator = new PrimaryKeySessionAuthenticator(null, [ + 'idField' => 'uuid', + 'identifierKey' => 'token', + ]); + $identifier = $authenticator->getIdentifier(); + + $this->assertInstanceOf(\Authentication\Identifier\TokenIdentifier::class, $identifier); + $this->assertSame('uuid', $identifier->getConfig('tokenField')); + $this->assertSame('token', $identifier->getConfig('dataField')); + } + /** * Test authentication * From a7fc2972b81ce20306aece599e71a56c8e87fbaf Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 14 Jan 2026 02:45:48 +0100 Subject: [PATCH 2/3] Fix code style - use import instead of FQCN --- .../Authenticator/PrimaryKeySessionAuthenticatorTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php index 7c5f05fd..ce704aae 100644 --- a/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php +++ b/tests/TestCase/Authenticator/PrimaryKeySessionAuthenticatorTest.php @@ -20,6 +20,7 @@ use Authentication\Authenticator\PrimaryKeySessionAuthenticator; use Authentication\Authenticator\Result; use Authentication\Identifier\IdentifierFactory; +use Authentication\Identifier\TokenIdentifier; use Cake\Http\Exception\UnauthorizedException; use Cake\Http\Response; use Cake\Http\ServerRequestFactory; @@ -123,7 +124,7 @@ public function testGetIdentifierReturnsDefaultWhenNotConfigured() $authenticator = new PrimaryKeySessionAuthenticator(null); $identifier = $authenticator->getIdentifier(); - $this->assertInstanceOf(\Authentication\Identifier\TokenIdentifier::class, $identifier); + $this->assertInstanceOf(TokenIdentifier::class, $identifier); $this->assertSame('id', $identifier->getConfig('tokenField')); $this->assertSame('key', $identifier->getConfig('dataField')); } @@ -141,7 +142,7 @@ public function testGetIdentifierUsesCustomConfig() ]); $identifier = $authenticator->getIdentifier(); - $this->assertInstanceOf(\Authentication\Identifier\TokenIdentifier::class, $identifier); + $this->assertInstanceOf(TokenIdentifier::class, $identifier); $this->assertSame('uuid', $identifier->getConfig('tokenField')); $this->assertSame('token', $identifier->getConfig('dataField')); } From ef15407b9d8deb011f92aa05447debb63de4ccc2 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 21 Jan 2026 17:06:34 +0100 Subject: [PATCH 3/3] Update docs for PrimaryKeySession default identifier --- docs/en/authenticators.rst | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/en/authenticators.rst b/docs/en/authenticators.rst index 83e785cc..86738e8e 100644 --- a/docs/en/authenticators.rst +++ b/docs/en/authenticators.rst @@ -37,23 +37,34 @@ It also helps to avoid session invalidation. Session itself stores the entity object including nested objects like DateTime or enums. With only the ID stored, the invalidation due to objects being modified will also dissolve. -Make sure to match this with a Token identifier with ``key``/``id`` keys:: +A default ``TokenIdentifier`` is provided that looks up users by their ``id`` field, +so minimal configuration is required:: + + $service->loadAuthenticator('Authentication.PrimaryKeySession'); + +Configuration options: + +- **idField**: The field in the database table to look up. Default is ``id``. +- **identifierKey**: The key used to store/retrieve the primary key from session data. + Default is ``key``. + +For custom lookup fields, the ``idField`` and ``identifierKey`` options propagate +to the default identifier automatically:: + + $service->loadAuthenticator('Authentication.PrimaryKeySession', [ + 'idField' => 'uuid', + ]); + +You can also provide a fully custom identifier configuration if needed:: $service->loadAuthenticator('Authentication.PrimaryKeySession', [ 'identifier' => [ 'Authentication.Token' => [ - 'tokenField' => 'id', // lookup for resolver and DB table - 'dataField' => 'key', // incoming data from authenticator + 'tokenField' => 'id', + 'dataField' => 'key', 'resolver' => 'Authentication.Orm', ], ], - 'urlChecker' => 'Authentication.CakeRouter', - 'loginUrl' => [ - 'prefix' => false, - 'plugin' => false, - 'controller' => 'Users', - 'action' => 'login', - ], ]); Form