Nette Documentation Preview

syntax
Аутентификация пользователей
****************************

<div class=perex>

Почти ни одно веб-приложение не обходится без механизма входа и выхода пользователей и проверки их прав. В этой главе мы поговорим о том:

- как выполнять вход и выход пользователей
- о собственных аутентификаторах

</div>

→ [Установка и требования |@home#Установка]

В примерах мы будем использовать объект класса [api:Nette\Security\User], представляющий текущего пользователя; получить его можно, попросив передать его через [внедрение зависимостей |dependency-injection:passing-dependencies]. В презентерах достаточно вызвать `$user = $this->getUser()`.


Аутентификация
==============

Аутентификация означает **вход пользователя**, то есть процесс, при котором проверяется личность пользователя. Обычно пользователь удостоверяет свою личность именем и паролем. Проверку выполняет так называемый [аутентификатор |#Аутентификатор]. Если вход не удаётся, выбрасывается `Nette\Security\AuthenticationException`.

```php
try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('Введённое имя пользователя или пароль неверны.');
}
```

Вот так вы выполняете выход пользователя:

```php
$user->logout();
```

А узнать, вошёл ли пользователь, можно так:

```php
echo $user->isLoggedIn() ? 'да' : 'нет';
```

Совсем просто, правда? А обо всех аспектах безопасности за вас заботится Nette.

В презентерах вы можете проверять вход в методе `startup()` и перенаправлять невошедших пользователей на страницу входа.

```php
protected function startup()
{
	parent::startup();
	if (!$this->getUser()->isLoggedIn()) {
		$this->redirect('Sign:in');
	}
}
```


Истечение срока
===============

Срок входа пользователя истекает вместе со сроком [хранилища |#Хранилище вошедшего пользователя], которым обычно является сессия (см. настройку [истечения сессии |http:configuration#Сессия]). Однако вы можете задать и более короткий промежуток времени, после которого пользователь будет разлогинен. Для этого служит метод `setExpiration()`, который вызывается перед `login()`. Передайте аргументом строку с относительным временем:

```php
// вход истекает после 30 минут бездействия
$user->setExpiration('30 minutes');

// отмена заданного срока
$user->setExpiration(null);
```

Метод `$user->getLogoutReason()` подскажет, был ли пользователь разлогинен из-за истечения промежутка времени. Он возвращает либо константу `Nette\Security\User::LogoutInactivity` (истекло ограничение по времени), либо `User::LogoutManual` (был вызван метод `logout()`).


Аутентификатор
==============

Это объект, который проверяет учётные данные для входа, обычно имя пользователя и пароль. Тривиальный его вид - класс [api:Nette\Security\SimpleAuthenticator], который можно определить в [конфигурации|configuration]:

```neon
security:
	users:
		# имя пользователя: пароль
		johndoe: 'secret123'
		kathy: 'evenmoresecretpassword'
```

Вместо паролей открытым текстом можно указать и их [хеши |passwords], см. [конфигурацию |configuration]. .{data-version:3.2.6}

Такое решение больше подходит для целей проверки. Мы покажем, как создать аутентификатор, который проверяет учётные данные по таблице базы данных.

Аутентификатор - это объект, реализующий интерфейс [api:Nette\Security\Authenticator] с методом `authenticate()`. Его задача - либо вернуть [личность |#Личность], либо выбросить `Nette\Security\AuthenticationException`. Можно было бы указать и код ошибки, чтобы точнее различить ситуацию: `Authenticator::IdentityNotFound` или `Authenticator::InvalidCredential`.

```php
use Nette;
use Nette\Security\SimpleIdentity;

class MyAuthenticator implements Nette\Security\Authenticator
{
	public function __construct(
		private Nette\Database\Explorer $database,
		private Nette\Security\Passwords $passwords,
	) {
	}

	public function authenticate(string $username, string $password): SimpleIdentity
	{
		$row = $this->database->table('users')
			->where('username', $username)
			->fetch();

		if (!$row) {
			throw new Nette\Security\AuthenticationException('Пользователь не найден.');
		}

		if (!$this->passwords->verify($password, $row->password)) {
			throw new Nette\Security\AuthenticationException('Неверный пароль.');
		}

		return new SimpleIdentity(
			$row->id,
			$row->role, // либо массив ролей
			['name' => $row->username],
		);
	}
}
```

Класс `MyAuthenticator` общается с базой данных через [Nette Database Explorer |database:explorer] и работает с таблицей `users`, где в столбце `username` находится имя пользователя для входа, а в столбце `password` - [хеш пароля |passwords]. После проверки имени и пароля он возвращает личность, содержащую ID пользователя, его роль (столбец `role` в таблице), о которой мы подробнее поговорим [позже |authorization#Роли], и массив с дополнительными данными (в нашем случае с именем пользователя).

Аутентификатор мы добавим в конфигурацию [как сервис |dependency-injection:services] DI-контейнера:

```neon
services:
	- MyAuthenticator
```


События $onLoggedIn, $onLoggedOut
---------------------------------

У объекта `Nette\Security\User` есть [события |nette:glossary#События] `$onLoggedIn` и `$onLoggedOut`, так что вы можете добавить callback'и, которые вызываются соответственно после успешного входа или после выхода пользователя.


```php
$user->onLoggedIn[] = function () {
	// пользователь только что вошёл
};
```


Личность
========

Личность (identity) - набор сведений о пользователе, который возвращает аутентификатор и который затем хранится в сессии и доступен через `$user->getIdentity()`. Так мы можем получить ID, роли и другие данные пользователя ровно в том виде, в каком передали их в аутентификаторе:

```php
$user->getIdentity()->getId();
// работает и сокращение $user->getId()

$user->getIdentity()->getRoles();

// данные пользователя доступны как свойства
// имя пользователя, которое мы передали в MyAuthenticator
$user->getIdentity()->name;
```

Важно, что при выходе через `$user->logout()` **личность не удаляется** и остаётся доступной. То есть, даже если у пользователя есть личность, он не обязан быть вошедшим. Если мы хотим удалить личность явно, мы выполняем выход вызовом `logout(true)`.

Благодаря этому вы по-прежнему можете предполагать, какой пользователь сидит за компьютером, и, например, показывать в интернет-магазине персонализированные предложения, но личные сведения показывать только после входа.

.{data-version:3.2.4}
Кроме очистки личности при отдельном вызове `logout(true)`, её сохранение можно полностью отключить свойством `$persistIdentity`. Если оно равно `false`, личность отбрасывается при каждом выходе и при истечении срока, так что `getIdentity()` тогда возвращает `null`. Сохранение личности зависит и от хранилища: хранилище cookie не может сохранить её после выхода, потому что всегда удаляет cookie.

Личность - объект, реализующий интерфейс [api:Nette\Security\IIdentity]. Реализация по умолчанию - [api:Nette\Security\SimpleIdentity]. И, как уже сказано, хранится она в сессии, так что если мы, например, изменим роль одного из вошедших пользователей, старые данные останутся в его личности, пока он не войдёт снова.


Хранилище вошедшего пользователя
================================

Два основных сведения о пользователе, а именно вошёл ли он и его [личность |#Личность], обычно передаются в сессии. Это можно изменить. За хранение этих сведений отвечает объект, реализующий интерфейс `Nette\Security\UserStorage`. Доступны две стандартные реализации: `Nette\Bridges\SecurityHttp\SessionStorage`, передающая данные в сессии, и `CookieStorage`, передающая данные в cookie. Выбрать хранилище и настроить его очень удобно в конфигурации [security › authentication |configuration#Хранилище пользователя].

Кроме того, вы можете повлиять на то, как именно будут проходить сохранение (*sleep*) и восстановление (*wakeup*) личности. Достаточно, чтобы аутентификатор реализовал интерфейс `Nette\Security\IdentityHandler`. Метод `sleepIdentity()` вызывается перед записью личности в хранилище, а `wakeupIdentity()` - после её чтения. Эти методы могут изменить содержимое личности либо заменить её новым объектом, который вернут. Метод `wakeupIdentity()` может даже вернуть `null`, что разлогинит пользователя. Интерфейс объявляет и метод `getGuestIdentity()`, см. [#Личность гостя].

В качестве примера покажем решение частого вопроса о том, как обновить роли в личности сразу после загрузки из сессии. В методе `wakeupIdentity()` мы передаём в личность актуальные роли, например из базы данных:

```php
final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// здесь можно изменить личность перед записью в хранилище после входа,
		// но сейчас нам это не нужно
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// обновляем роли в личности
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}

	public function getGuestIdentity(): ?IIdentity
	{
		// личность гостя здесь не используется
		return null;
	}
```

Теперь вернёмся к хранилищу на основе cookie. Оно позволяет создать сайт, на котором пользователи могут входить, а сессии при этом не нужны. То есть писать на диск не требуется. Именно так работает сайт, который вы сейчас читаете, включая форум. В этом случае реализация `IdentityHandler` необходима. В cookie мы будем хранить только случайный токен, представляющий вошедшего пользователя.

Сначала задайте нужное хранилище в конфигурации через `security › authentication › storage: cookie`.

В базе данных создайте столбец `authtoken`, где у каждого пользователя будет [совершенно случайная, уникальная и неугадываемая |utils:random] строка достаточной длины (не менее 13 символов). `CookieStorage` передаёт в cookie только значение `$identity->getId()`, поэтому в `sleepIdentity()` мы заменяем исходную личность личностью-заместителем, содержащей в ID `authtoken`. И наоборот, в методе `wakeupIdentity()` мы считываем всю личность из базы данных по authtoken:

```php
final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function authenticate(string $username, string $password): SimpleIdentity
	{
		$row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username);
		// проверяем пароль
		// ...
		// возвращаем личность со всеми данными из базы данных
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// возвращаем личность-заместитель, где в ID находится authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// заменяем личность-заместитель полной личностью, как в authenticate()
		$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row)
			: null;
	}

	public function getGuestIdentity(): ?IIdentity
	{
		// личность гостя здесь не используется
		return null;
	}
}
```


Личность гостя .{data-version:3.2.4}
====================================

Иногда удобно, чтобы личность была и у посетителей, которые не вошли, например чтобы дать им набор ролей по умолчанию или какие-то данные. Если аутентификатор реализует `IdentityHandler`, он может предоставить её методом `getGuestIdentity()`, который используется всегда, когда никто не вошёл. Тогда `getIdentity()`, `getId()` и `getRoles()` откатываются к ней, так что у гостей могут быть собственные роли вместо простой роли `guest`. Верните `null`, если личность гостя вам не нужна.

```php
public function getGuestIdentity(): ?IIdentity
{
	return new SimpleIdentity('guest', ['guest'], ['name' => 'Guest']);
}
```

Личность гостя никогда не сохраняется в хранилище, и вход всегда её заменяет.


Несколько независимых входов
============================

В рамках одного сайта и одной сессии может одновременно входить несколько независимых пользователей. Например, если мы хотим иметь отдельную аутентификацию для администрирования и для публичной части сайта, достаточно задать каждой из них уникальное пространство имён:

```php
$user->getStorage()->setNamespace('backend');
```

Важно не забыть задавать пространство имён всегда и во всех местах, относящихся к соответствующей части. Если мы используем презентеры, пространство имён мы задаём в общем предке этой части, обычно в BasePresenter. Делаем мы это расширением метода [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]:

```php
public function checkRequirements($element): void
{
	$this->getUser()->getStorage()->setNamespace('backend');
	parent::checkRequirements($element);
}
```

Если вы переключаете пространство имён в рамках одного запроса (после того, как состояние аутентификации уже прочитано), объект `User` по-прежнему держит состояние, закешированное из предыдущего пространства имён. В таком случае вызовите `refreshStorage()`, чтобы сбросить кеш и заставить перечитать состояние из нового пространства имён:

```php
$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // перечитываем состояние из нового пространства имён
```


Несколько аутентификаторов
--------------------------

Разделение приложения на части с независимым входом обычно требует и разных аутентификаторов. Однако если бы мы зарегистрировали в конфигурации сервисов два класса, реализующих Authenticator, Nette не знала бы, какой из них автоматически передать объекту `Nette\Security\User`, и показала бы ошибку. Поэтому нам нужно ограничить [autowiring |dependency-injection:autowiring] для аутентификаторов так, чтобы он работал, только когда кто-то запрашивает конкретный класс, например `FrontAuthenticator`. Достигается это выбором `autowired: self`:

```neon
services:
	-
		create: FrontAuthenticator
		autowired: self
```

```php
class SignPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private FrontAuthenticator $authenticator,
	) {
	}
}
```

Аутентификатор объекта User мы задаём перед вызовом метода [login() |api:Nette\Security\User::login()], то есть обычно в коде формы, которая выполняет вход:

```php
$form->onSuccess[] = function (Form $form, \stdClass $data) {
	$user = $this->getUser();
	$user->setAuthenticator($this->authenticator);
	$user->login($data->username, $data->password);
	// ...
};
```

Аутентификация пользователей

Почти ни одно веб-приложение не обходится без механизма входа и выхода пользователей и проверки их прав. В этой главе мы поговорим о том:

  • как выполнять вход и выход пользователей
  • о собственных аутентификаторах

Установка и требования

В примерах мы будем использовать объект класса Nette\Security\User, представляющий текущего пользователя; получить его можно, попросив передать его через внедрение зависимостей. В презентерах достаточно вызвать $user = $this->getUser().

Аутентификация

Аутентификация означает вход пользователя, то есть процесс, при котором проверяется личность пользователя. Обычно пользователь удостоверяет свою личность именем и паролем. Проверку выполняет так называемый аутентификатор. Если вход не удаётся, выбрасывается Nette\Security\AuthenticationException.

try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('Введённое имя пользователя или пароль неверны.');
}

Вот так вы выполняете выход пользователя:

$user->logout();

А узнать, вошёл ли пользователь, можно так:

echo $user->isLoggedIn() ? 'да' : 'нет';

Совсем просто, правда? А обо всех аспектах безопасности за вас заботится Nette.

В презентерах вы можете проверять вход в методе startup() и перенаправлять невошедших пользователей на страницу входа.

protected function startup()
{
	parent::startup();
	if (!$this->getUser()->isLoggedIn()) {
		$this->redirect('Sign:in');
	}
}

Истечение срока

Срок входа пользователя истекает вместе со сроком хранилища, которым обычно является сессия (см. настройку истечения сессии). Однако вы можете задать и более короткий промежуток времени, после которого пользователь будет разлогинен. Для этого служит метод setExpiration(), который вызывается перед login(). Передайте аргументом строку с относительным временем:

// вход истекает после 30 минут бездействия
$user->setExpiration('30 minutes');

// отмена заданного срока
$user->setExpiration(null);

Метод $user->getLogoutReason() подскажет, был ли пользователь разлогинен из-за истечения промежутка времени. Он возвращает либо константу Nette\Security\User::LogoutInactivity (истекло ограничение по времени), либо User::LogoutManual (был вызван метод logout()).

Аутентификатор

Это объект, который проверяет учётные данные для входа, обычно имя пользователя и пароль. Тривиальный его вид – класс Nette\Security\SimpleAuthenticator, который можно определить в конфигурации:

security:
	users:
		# имя пользователя: пароль
		johndoe: 'secret123'
		kathy: 'evenmoresecretpassword'

Вместо паролей открытым текстом можно указать и их хеши, см. конфигурацию.

Такое решение больше подходит для целей проверки. Мы покажем, как создать аутентификатор, который проверяет учётные данные по таблице базы данных.

Аутентификатор – это объект, реализующий интерфейс Nette\Security\Authenticator с методом authenticate(). Его задача – либо вернуть личность, либо выбросить Nette\Security\AuthenticationException. Можно было бы указать и код ошибки, чтобы точнее различить ситуацию: Authenticator::IdentityNotFound или Authenticator::InvalidCredential.

use Nette;
use Nette\Security\SimpleIdentity;

class MyAuthenticator implements Nette\Security\Authenticator
{
	public function __construct(
		private Nette\Database\Explorer $database,
		private Nette\Security\Passwords $passwords,
	) {
	}

	public function authenticate(string $username, string $password): SimpleIdentity
	{
		$row = $this->database->table('users')
			->where('username', $username)
			->fetch();

		if (!$row) {
			throw new Nette\Security\AuthenticationException('Пользователь не найден.');
		}

		if (!$this->passwords->verify($password, $row->password)) {
			throw new Nette\Security\AuthenticationException('Неверный пароль.');
		}

		return new SimpleIdentity(
			$row->id,
			$row->role, // либо массив ролей
			['name' => $row->username],
		);
	}
}

Класс MyAuthenticator общается с базой данных через Nette Database Explorer и работает с таблицей users, где в столбце username находится имя пользователя для входа, а в столбце password – хеш пароля. После проверки имени и пароля он возвращает личность, содержащую ID пользователя, его роль (столбец role в таблице), о которой мы подробнее поговорим позже, и массив с дополнительными данными (в нашем случае с именем пользователя).

Аутентификатор мы добавим в конфигурацию как сервис DI-контейнера:

services:
	- MyAuthenticator

События $onLoggedIn, $onLoggedOut

У объекта Nette\Security\User есть события $onLoggedIn и $onLoggedOut, так что вы можете добавить callback'и, которые вызываются соответственно после успешного входа или после выхода пользователя.

$user->onLoggedIn[] = function () {
	// пользователь только что вошёл
};

Личность

Личность (identity) – набор сведений о пользователе, который возвращает аутентификатор и который затем хранится в сессии и доступен через $user->getIdentity(). Так мы можем получить ID, роли и другие данные пользователя ровно в том виде, в каком передали их в аутентификаторе:

$user->getIdentity()->getId();
// работает и сокращение $user->getId()

$user->getIdentity()->getRoles();

// данные пользователя доступны как свойства
// имя пользователя, которое мы передали в MyAuthenticator
$user->getIdentity()->name;

Важно, что при выходе через $user->logout() личность не удаляется и остаётся доступной. То есть, даже если у пользователя есть личность, он не обязан быть вошедшим. Если мы хотим удалить личность явно, мы выполняем выход вызовом logout(true).

Благодаря этому вы по-прежнему можете предполагать, какой пользователь сидит за компьютером, и, например, показывать в интернет-магазине персонализированные предложения, но личные сведения показывать только после входа.

Кроме очистки личности при отдельном вызове logout(true), её сохранение можно полностью отключить свойством $persistIdentity. Если оно равно false, личность отбрасывается при каждом выходе и при истечении срока, так что getIdentity() тогда возвращает null. Сохранение личности зависит и от хранилища: хранилище cookie не может сохранить её после выхода, потому что всегда удаляет cookie.

Личность – объект, реализующий интерфейс Nette\Security\IIdentity. Реализация по умолчанию – Nette\Security\SimpleIdentity. И, как уже сказано, хранится она в сессии, так что если мы, например, изменим роль одного из вошедших пользователей, старые данные останутся в его личности, пока он не войдёт снова.

Хранилище вошедшего пользователя

Два основных сведения о пользователе, а именно вошёл ли он и его личность, обычно передаются в сессии. Это можно изменить. За хранение этих сведений отвечает объект, реализующий интерфейс Nette\Security\UserStorage. Доступны две стандартные реализации: Nette\Bridges\SecurityHttp\SessionStorage, передающая данные в сессии, и CookieStorage, передающая данные в cookie. Выбрать хранилище и настроить его очень удобно в конфигурации security › authentication.

Кроме того, вы можете повлиять на то, как именно будут проходить сохранение (sleep) и восстановление (wakeup) личности. Достаточно, чтобы аутентификатор реализовал интерфейс Nette\Security\IdentityHandler. Метод sleepIdentity() вызывается перед записью личности в хранилище, а wakeupIdentity() – после её чтения. Эти методы могут изменить содержимое личности либо заменить её новым объектом, который вернут. Метод wakeupIdentity() может даже вернуть null, что разлогинит пользователя. Интерфейс объявляет и метод getGuestIdentity(), см. Личность гостя.

В качестве примера покажем решение частого вопроса о том, как обновить роли в личности сразу после загрузки из сессии. В методе wakeupIdentity() мы передаём в личность актуальные роли, например из базы данных:

final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// здесь можно изменить личность перед записью в хранилище после входа,
		// но сейчас нам это не нужно
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// обновляем роли в личности
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}

	public function getGuestIdentity(): ?IIdentity
	{
		// личность гостя здесь не используется
		return null;
	}

Теперь вернёмся к хранилищу на основе cookie. Оно позволяет создать сайт, на котором пользователи могут входить, а сессии при этом не нужны. То есть писать на диск не требуется. Именно так работает сайт, который вы сейчас читаете, включая форум. В этом случае реализация IdentityHandler необходима. В cookie мы будем хранить только случайный токен, представляющий вошедшего пользователя.

Сначала задайте нужное хранилище в конфигурации через security › authentication › storage: cookie.

В базе данных создайте столбец authtoken, где у каждого пользователя будет совершенно случайная, уникальная и неугадываемая строка достаточной длины (не менее 13 символов). CookieStorage передаёт в cookie только значение $identity->getId(), поэтому в sleepIdentity() мы заменяем исходную личность личностью-заместителем, содержащей в ID authtoken. И наоборот, в методе wakeupIdentity() мы считываем всю личность из базы данных по authtoken:

final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function authenticate(string $username, string $password): SimpleIdentity
	{
		$row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username);
		// проверяем пароль
		// ...
		// возвращаем личность со всеми данными из базы данных
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// возвращаем личность-заместитель, где в ID находится authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// заменяем личность-заместитель полной личностью, как в authenticate()
		$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row)
			: null;
	}

	public function getGuestIdentity(): ?IIdentity
	{
		// личность гостя здесь не используется
		return null;
	}
}

Личность гостя

Иногда удобно, чтобы личность была и у посетителей, которые не вошли, например чтобы дать им набор ролей по умолчанию или какие-то данные. Если аутентификатор реализует IdentityHandler, он может предоставить её методом getGuestIdentity(), который используется всегда, когда никто не вошёл. Тогда getIdentity(), getId() и getRoles() откатываются к ней, так что у гостей могут быть собственные роли вместо простой роли guest. Верните null, если личность гостя вам не нужна.

public function getGuestIdentity(): ?IIdentity
{
	return new SimpleIdentity('guest', ['guest'], ['name' => 'Guest']);
}

Личность гостя никогда не сохраняется в хранилище, и вход всегда её заменяет.

Несколько независимых входов

В рамках одного сайта и одной сессии может одновременно входить несколько независимых пользователей. Например, если мы хотим иметь отдельную аутентификацию для администрирования и для публичной части сайта, достаточно задать каждой из них уникальное пространство имён:

$user->getStorage()->setNamespace('backend');

Важно не забыть задавать пространство имён всегда и во всех местах, относящихся к соответствующей части. Если мы используем презентеры, пространство имён мы задаём в общем предке этой части, обычно в BasePresenter. Делаем мы это расширением метода checkRequirements():

public function checkRequirements($element): void
{
	$this->getUser()->getStorage()->setNamespace('backend');
	parent::checkRequirements($element);
}

Если вы переключаете пространство имён в рамках одного запроса (после того, как состояние аутентификации уже прочитано), объект User по-прежнему держит состояние, закешированное из предыдущего пространства имён. В таком случае вызовите refreshStorage(), чтобы сбросить кеш и заставить перечитать состояние из нового пространства имён:

$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // перечитываем состояние из нового пространства имён

Несколько аутентификаторов

Разделение приложения на части с независимым входом обычно требует и разных аутентификаторов. Однако если бы мы зарегистрировали в конфигурации сервисов два класса, реализующих Authenticator, Nette не знала бы, какой из них автоматически передать объекту Nette\Security\User, и показала бы ошибку. Поэтому нам нужно ограничить autowiring для аутентификаторов так, чтобы он работал, только когда кто-то запрашивает конкретный класс, например FrontAuthenticator. Достигается это выбором autowired: self:

services:
	-
		create: FrontAuthenticator
		autowired: self
class SignPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private FrontAuthenticator $authenticator,
	) {
	}
}

Аутентификатор объекта User мы задаём перед вызовом метода login(), то есть обычно в коде формы, которая выполняет вход:

$form->onSuccess[] = function (Form $form, \stdClass $data) {
	$user = $this->getUser();
	$user->setAuthenticator($this->authenticator);
	$user->login($data->username, $data->password);
	// ...
};