Skip to content

Add PrimaryKeySessionAuthenticator #710

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

Open
wants to merge 3 commits into
base: 3.next
Choose a base branch
from
Open
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
30 changes: 30 additions & 0 deletions docs/en/authenticators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,36 @@ Configuration options:
identifier in your user storage. Defaults to ``username``. This option is
used when the ``identify`` option is set to true.

PrimaryKeySession
=================

This is an improved version of Session that will only store the primary key.
This way the data will always be fetched fresh from the DB and issues like
having to update the identity when updating account data should be gone.

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::

$service->loadAuthenticator('Authentication.PrimaryKeySession', [
'identifier' => [
'Authentication.Token' => [
'tokenField' => 'id', // lookup for resolver and DB table
'dataField' => 'key', // incoming data from authenticator
'resolver' => 'Authentication.Orm',
],
],
'urlChecker' => 'Authentication.CakeRouter',
'loginUrl' => [
'prefix' => false,
'plugin' => false,
'controller' => 'Users',
'action' => 'login',
],
]);

Form
====

Expand Down
19 changes: 16 additions & 3 deletions docs/en/url-checkers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ URL Checkers
############

To provide an abstract and framework agnostic solution there are URL
checkers implemented that allow you to customize the comparision of the
checkers implemented that allow you to customize the comparison of the
current URL if needed. For example to another frameworks routing.

Included Checkers
Expand All @@ -24,11 +24,24 @@ Options:
CakeRouterUrlChecker
--------------------

Options:

Use this checker if you want to use the array notation of CakePHPs
routing system. The checker also works with named routes.

$service->loadAuthenticator('Authentication.Form', [
'urlChecker' => 'Authentication.CakeRouter',
'fields' => [
AbstractIdentifier::CREDENTIAL_USERNAME => 'email',
AbstractIdentifier::CREDENTIAL_PASSWORD => 'password',
],
'loginUrl' => [
'prefix' => false,
'plugin' => false,
'controller' => 'Users',
'action' => 'login',
],
]);

Options:
- **checkFullUrl**: To compare the full URL, including protocol, host
and port or not. Default is ``false``

Expand Down
110 changes: 110 additions & 0 deletions src/Authenticator/PrimaryKeySessionAuthenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);

namespace Authentication\Authenticator;

use ArrayAccess;
use Authentication\Identifier\IdentifierInterface;
use Cake\Http\Exception\UnauthorizedException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* Session Authenticator with only ID
*/
class PrimaryKeySessionAuthenticator extends SessionAuthenticator
{
/**
* @param \Authentication\Identifier\IdentifierInterface $identifier
* @param array<string, mixed> $config
*/
public function __construct(IdentifierInterface $identifier, array $config = [])
{
$config += [
'identifierKey' => 'key',
Copy link
Member

Choose a reason for hiding this comment

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

If you're inheriting from SessionAuthenticator how does identifierKey interact with fields?

What is key here? I don't see that as a field in the auth_users table that your tests use. Is it primarily to be aligned with the identifier configuration?

Copy link
Member Author

Choose a reason for hiding this comment

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

key here maps to the dataField of the identifier, this is how they pass in the data.

Copy link
Member Author

Choose a reason for hiding this comment

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

fields is unused afaik.

'idField' => 'id',
];

parent::__construct($identifier, $config);
}

/**
* Authenticate a user using session data.
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request to authenticate with.
* @return \Authentication\Authenticator\ResultInterface
*/
public function authenticate(ServerRequestInterface $request): ResultInterface
{
$sessionKey = $this->getConfig('sessionKey');
/** @var \Cake\Http\Session $session */
$session = $request->getAttribute('session');

$userId = $session->read($sessionKey);
if (!$userId) {
return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND);
}

$user = $this->_identifier->identify([$this->getConfig('identifierKey') => $userId]);
if (!$user) {
return new Result(null, Result::FAILURE_IDENTITY_NOT_FOUND);
}

return new Result($user, Result::SUCCESS);
}

/**
* @inheritDoc
*/
public function persistIdentity(ServerRequestInterface $request, ResponseInterface $response, $identity): array
{
$sessionKey = $this->getConfig('sessionKey');
/** @var \Cake\Http\Session $session */
$session = $request->getAttribute('session');

if (!$session->check($sessionKey)) {
$session->renew();
$session->write($sessionKey, $identity[$this->getConfig('idField')]);
}

return [
'request' => $request,
'response' => $response,
];
}

/**
* Impersonates a user
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request
* @param \Psr\Http\Message\ResponseInterface $response The response
* @param \ArrayAccess $impersonator User who impersonates
* @param \ArrayAccess $impersonated User impersonated
* @return array
*/
public function impersonate(
ServerRequestInterface $request,
ResponseInterface $response,
ArrayAccess $impersonator,
ArrayAccess $impersonated,
): array {
$sessionKey = $this->getConfig('sessionKey');
$impersonateSessionKey = $this->getConfig('impersonateSessionKey');
/** @var \Cake\Http\Session $session */
$session = $request->getAttribute('session');
if ($session->check($impersonateSessionKey)) {
throw new UnauthorizedException(
'You are impersonating a user already. ' .
'Stop the current impersonation before impersonating another user.',
);
}
$session->write($impersonateSessionKey, $impersonator[$this->getConfig('idField')]);
$session->write($sessionKey, $impersonated[$this->getConfig('idField')]);
$this->setConfig('identify', true);

return [
'request' => $request,
'response' => $response,
];
}
}
Loading