Repository navigation
Next major: add preserveImpersonation option to setIdentity() / clearIdentity() #797
Description
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 onPersistenceInterface, adding the parameter there is a hard BC break and is why this is deferred to the major. - Tests for both the
preserveImpersonation: truepersist case and the default-ends-impersonation case existed in the PR branch history and can be lifted from there.
Activity
bancer commentedon Aug 28, 2026
FYI: There was a bug fixed in 2.x somewhat related to this code in
setIdentitymethod that should also be applied in 3.x and 4.x - https://github.com/cakephp/authentication/pull/802/changes#diff-2698b9df1a0da8e0f9d89693019bb0202b2171aedf2fab334376029926a209f0R283-R287