Background
PR #788 added AuthenticationComponent::replaceIdentity() for the common request-only identity refresh case (e.g. attaching eager-loaded associations in beforeFilter() without ending an active impersonation).
An earlier iteration of that PR also expanded two existing signatures:
AuthenticationComponent::setIdentity(ArrayAccess|array $identity, bool $preserveImpersonation = false)
AuthenticationService::clearIdentity(ServerRequestInterface $request, ResponseInterface $response, bool $stopImpersonation = true)
Those were reverted before merge to keep the public API surface unchanged on the 4.x minor. The signature growth belongs in the next major, where adding the flag to PersistenceInterface::clearIdentity() itself is clean.
Proposal for the next major
Add a first-class "persist a refreshed identity while keeping impersonation alive" path. Unlike replaceIdentity() (request attribute only, lost on the next request), this survives into subsequent requests.
1. Extend the interface
// PersistenceInterface
public function clearIdentity(
ServerRequestInterface $request,
ResponseInterface $response,
bool $stopImpersonation = true,
): array;
2. Honor the flag in AuthenticationService::clearIdentity()
public function clearIdentity(
ServerRequestInterface $request,
ResponseInterface $response,
bool $stopImpersonation = true,
): array {
foreach ($this->authenticators() as $authenticator) {
if ($authenticator instanceof PersistenceInterface) {
if (
$stopImpersonation
&& $authenticator instanceof ImpersonationInterface
&& $authenticator->isImpersonating($request)
) {
$stopImpersonationResult = $authenticator->stopImpersonating($request, $response);
['request' => $request, 'response' => $response] = $stopImpersonationResult;
}
$result = $authenticator->clearIdentity($request, $response);
['request' => $request, 'response' => $response] = $result;
}
}
if ($stopImpersonation) {
$this->_successfulAuthenticator = null;
}
return [
'request' => $request->withoutAttribute($this->getConfig('identityAttribute')),
'response' => $response,
];
}
3. Add the opt-in to the component
public function setIdentity(ArrayAccess|array $identity, bool $preserveImpersonation = false)
{
$controller = $this->getController();
$service = $this->getAuthenticationService();
$service->clearIdentity(
$controller->getRequest(),
$controller->getResponse(),
stopImpersonation: !$preserveImpersonation,
);
/** @var array{request: \Cake\Http\ServerRequest, response: \Cake\Http\Response} $result */
$result = $service->persistIdentity(
$controller->getRequest(),
$controller->getResponse(),
$identity,
);
$controller->setRequest($result['request']);
$controller->setResponse($result['response']);
return $this;
}
Usage
// Refresh the impersonated user and keep it across requests, without
// reverting to the impersonator:
$this->Authentication->setIdentity($reloaded, preserveImpersonation: true);
Note this also skips the session rotation that the default setIdentity() flow performs - it is a refresh, not a privilege transition, so it must not be used for login or role changes. That caveat should be documented.
Notes
- Because
clearIdentity() is on PersistenceInterface, adding the parameter there is a hard BC break and is why this is deferred to the major.
- Tests for both the
preserveImpersonation: true persist case and the default-ends-impersonation case existed in the PR branch history and can be lifted from there.
Background
PR #788 added
AuthenticationComponent::replaceIdentity()for the common request-only identity refresh case (e.g. attaching eager-loaded associations inbeforeFilter()without ending an active impersonation).An earlier iteration of that PR also expanded two existing signatures:
AuthenticationComponent::setIdentity(ArrayAccess|array $identity, bool $preserveImpersonation = false)AuthenticationService::clearIdentity(ServerRequestInterface $request, ResponseInterface $response, bool $stopImpersonation = true)Those were reverted before merge to keep the public API surface unchanged on the
4.xminor. The signature growth belongs in the next major, where adding the flag toPersistenceInterface::clearIdentity()itself is clean.Proposal for the next major
Add a first-class "persist a refreshed identity while keeping impersonation alive" path. Unlike
replaceIdentity()(request attribute only, lost on the next request), this survives into subsequent requests.1. Extend the interface
2. Honor the flag in
AuthenticationService::clearIdentity()3. Add the opt-in to the component
Usage
Note this also skips the session rotation that the default
setIdentity()flow performs - it is a refresh, not a privilege transition, so it must not be used for login or role changes. That caveat should be documented.Notes
clearIdentity()is onPersistenceInterface, adding the parameter there is a hard BC break and is why this is deferred to the major.preserveImpersonation: truepersist case and the default-ends-impersonation case existed in the PR branch history and can be lifted from there.