Nette Documentation Preview

syntax
Autenticazione degli utenti
***************************

<div class=perex>

Quasi nessuna applicazione web può fare a meno di un meccanismo per far accedere e disconnettere gli utenti e per verificarne i permessi. In questo capitolo parleremo di:

- accesso e disconnessione degli utenti
- autenticatori personalizzati

</div>

→ [Installazione e requisiti |@home#Installazione]

Negli esempi useremo un oggetto della classe [api:Nette\Security\User], che rappresenta l'utente corrente e che ottenete facendovelo passare con la [dependency injection |dependency-injection:passing-dependencies]. Nei presenter basta chiamare `$user = $this->getUser()`.


Autenticazione
==============

Autenticazione significa **accesso dell'utente**, cioè il processo con cui si verifica l'identità di un utente. L'utente si identifica di solito con nome utente e password. La verifica la esegue il cosiddetto [#Autenticatore]. Se l'accesso fallisce, viene lanciata una `Nette\Security\AuthenticationException`.

```php
try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('Il nome utente o la password inseriti non sono corretti.');
}
```

L'utente si disconnette così:

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

E per sapere se l'utente è connesso:

```php
echo $user->isLoggedIn() ? 'sì' : 'no';
```

Molto semplice, vero? E di tutti gli aspetti di sicurezza si occupa Nette per voi.

Nei presenter potete verificare l'accesso nel metodo `startup()` e reindirizzare gli utenti non connessi alla pagina di accesso.

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


Scadenza
========

L'accesso dell'utente scade insieme alla [scadenza dello storage |#Storage dell'utente connesso], che di solito è la sessione (vedi l'impostazione della [scadenza della sessione |http:configuration#Sessione]). Potete però impostare anche un intervallo di tempo più breve dopo il quale l'utente viene disconnesso. A questo serve il metodo `setExpiration()`, che si chiama prima di `login()`. Come argomento passate una stringa con un tempo relativo:

```php
// l'accesso scade dopo 30 minuti di inattività
$user->setExpiration('30 minutes');

// annulla la scadenza impostata
$user->setExpiration(null);
```

Il metodo `$user->getLogoutReason()` rivela se l'utente è stato disconnesso perché l'intervallo di tempo è scaduto. Restituisce la costante `Nette\Security\User::LogoutInactivity` (è scaduto il limite di tempo) oppure `User::LogoutManual` (è stato chiamato il metodo `logout()`).


Autenticatore
=============

È un oggetto che verifica le credenziali di accesso, tipicamente nome utente e password. Una forma banale è la classe [api:Nette\Security\SimpleAuthenticator], che si può definire nella [configurazione|configuration]:

```neon
security:
	users:
		# nome utente: password
		johndoe: 'secret123'
		kathy: 'evenmoresecretpassword'
```

Invece delle password in chiaro potete indicare anche i loro [hash |passwords]; vedi la [configurazione |configuration]. .{data-version:3.2.6}

Questa soluzione è più adatta agli scopi di prova. Vi mostreremo come creare un autenticatore che verifica le credenziali di accesso rispetto a una tabella del database.

Un autenticatore è un oggetto che implementa l'interfaccia [api:Nette\Security\Authenticator] con il metodo `authenticate()`. Il suo compito è restituire un'[identità |#Identità] oppure lanciare una `Nette\Security\AuthenticationException`. Si potrebbe anche indicare un codice di errore per distinguere la situazione in modo più fine: `Authenticator::IdentityNotFound` oppure `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('Utente non trovato.');
		}

		if (!$this->passwords->verify($password, $row->password)) {
			throw new Nette\Security\AuthenticationException('Password non valida.');
		}

		return new SimpleIdentity(
			$row->id,
			$row->role, // oppure un array di ruoli
			['name' => $row->username],
		);
	}
}
```

La classe `MyAuthenticator` comunica con il database tramite [Nette Database Explorer |database:explorer] e lavora con la tabella `users`, dove la colonna `username` contiene il nome di accesso dell'utente e la colonna `password` l'[hash della password |passwords]. Dopo aver verificato nome e password restituisce l'identità, che contiene l'ID dell'utente, il suo ruolo (la colonna `role` nella tabella), di cui parleremo [più avanti |authorization#Ruoli], e un array con altri dati (nel nostro caso il nome utente).

L'autenticatore lo aggiungiamo alla configurazione [come servizio |dependency-injection:services] del container DI:

```neon
services:
	- MyAuthenticator
```


Eventi $onLoggedIn, $onLoggedOut
--------------------------------

L'oggetto `Nette\Security\User` ha gli [eventi |nette:glossary#Eventi] `$onLoggedIn` e `$onLoggedOut`, quindi potete aggiungere callback che si attivano rispettivamente dopo un accesso riuscito o dopo la disconnessione dell'utente.


```php
$user->onLoggedIn[] = function () {
	// l'utente ha appena effettuato l'accesso
};
```


Identità
========

L'identità è un insieme di informazioni sull'utente restituito dall'autenticatore, che viene poi conservato nella sessione e si può ottenere con `$user->getIdentity()`. Possiamo così ottenere l'ID, i ruoli e altri dati dell'utente, proprio come li abbiamo passati nell'autenticatore:

```php
$user->getIdentity()->getId();
// funziona anche la scorciatoia $user->getId()

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

// i dati dell'utente sono accessibili come proprietà
// il nome utente che abbiamo passato in MyAuthenticator
$user->getIdentity()->name;
```

Cosa importante: alla disconnessione con `$user->logout()` **l'identità non viene cancellata** e resta disponibile. Quindi, anche se un utente ha un'identità, non deve per forza essere connesso. Se vogliamo cancellare esplicitamente l'identità, disconnettiamo l'utente chiamando `logout(true)`.

Grazie a questo potete continuare a supporre quale utente sia al computer e mostrargli per esempio offerte personalizzate in un e-shop, ma le sue informazioni personali le potete mostrare solo dopo che ha effettuato l'accesso.

.{data-version:3.2.4}
Oltre a cancellare l'identità di volta in volta con `logout(true)`, potete disattivarne del tutto la conservazione con la proprietà `$persistIdentity`. Impostata a `false`, l'identità viene scartata a ogni disconnessione e alla scadenza, quindi `getIdentity()` restituisce poi `null`. La conservazione dell'identità dipende anche dallo storage: lo storage su cookie non può conservarla dopo la disconnessione, perché cancella sempre il cookie.

L'identità è un oggetto che implementa l'interfaccia [api:Nette\Security\IIdentity]. L'implementazione predefinita è [api:Nette\Security\SimpleIdentity]. E, come detto, viene mantenuta nella sessione, quindi se per esempio cambiamo il ruolo di uno degli utenti connessi, i vecchi dati resteranno nella sua identità finché non effettuerà di nuovo l'accesso.


Storage dell'utente connesso
============================

Le due informazioni fondamentali sull'utente, cioè se è connesso e la sua [identità |#Identità], vengono di solito trasmesse nella sessione. Il che si può cambiare. Della conservazione di queste informazioni si occupa un oggetto che implementa l'interfaccia `Nette\Security\UserStorage`. Sono disponibili due implementazioni standard: `Nette\Bridges\SecurityHttp\SessionStorage`, che trasmette i dati nella sessione, e `CookieStorage`, che li trasmette in un cookie. Lo storage lo potete scegliere e configurare molto comodamente nella configurazione [security › authentication |configuration#Storage dell'utente].

Potete inoltre influire su come avvengono esattamente il salvataggio (*sleep*) e il ripristino (*wakeup*) dell'identità. Basta che l'autenticatore implementi l'interfaccia `Nette\Security\IdentityHandler`. Il metodo `sleepIdentity()` viene chiamato prima che l'identità venga scritta nello storage e `wakeupIdentity()` dopo che è stata letta. Questi metodi possono modificare il contenuto dell'identità, oppure sostituirla con un nuovo oggetto che restituiscono. Il metodo `wakeupIdentity()` può perfino restituire `null`, il che disconnette l'utente. L'interfaccia dichiara anche il metodo `getGuestIdentity()`, vedi [#Identità ospite].

Come esempio mostriamo la soluzione alla domanda frequente su come aggiornare i ruoli nell'identità subito dopo il caricamento dalla sessione. Nel metodo `wakeupIdentity()` passiamo nell'identità i ruoli attuali, per esempio da un database:

```php
final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// qui potete modificare l'identità prima di scriverla nello storage dopo l'accesso,
		// ma ora non ci serve
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// aggiorniamo i ruoli nell'identità
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}

	public function getGuestIdentity(): ?IIdentity
	{
		// qui non si usa alcuna identità ospite
		return null;
	}
```

Torniamo ora allo storage basato sui cookie. Permette di creare un sito in cui gli utenti possono accedere senza aver bisogno delle sessioni. Non deve quindi scrivere su disco. È così che funziona il sito che state leggendo, forum compreso. In questo caso l'implementazione di `IdentityHandler` è indispensabile. Nel cookie salveremo solo un token casuale che rappresenta l'utente connesso.

Per prima cosa impostate nella configurazione lo storage necessario con `security › authentication › storage: cookie`.

Nel database create la colonna `authtoken`, in cui ogni utente avrà una stringa [del tutto casuale, univoca e non indovinabile |utils:random] di lunghezza sufficiente (almeno 13 caratteri). Il `CookieStorage` trasmette nel cookie solo il valore `$identity->getId()`, quindi in `sleepIdentity()` sostituiamo l'identità originale con un'identità proxy che contiene l'`authtoken` nell'ID. Al contrario, nel metodo `wakeupIdentity()` leggiamo dal database l'intera identità in base all'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);
		// verifichiamo la password
		// ...
		// restituiamo l'identità con tutti i dati dal database
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// restituiamo un'identità proxy in cui l'ID contiene l'authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// sostituiamo l'identità proxy con quella completa, come in 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
	{
		// qui non si usa alcuna identità ospite
		return null;
	}
}
```


Identità ospite .{data-version:3.2.4}
=====================================

A volte torna comodo che anche i visitatori non connessi abbiano un'identità, per esempio per dare loro un insieme predefinito di ruoli o qualche dato. Se l'autenticatore implementa `IdentityHandler`, può fornirne una con il metodo `getGuestIdentity()`, che viene usato ogni volta che nessuno è connesso. Allora `getIdentity()`, `getId()` e `getRoles()` ripiegano su di essa, così gli ospiti possono avere ruoli propri invece del semplice ruolo `guest`. Restituite `null` se non volete un'identità ospite.

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

L'identità ospite non viene mai salvata nello storage e l'accesso la sostituisce sempre.


Più accessi indipendenti
========================

È possibile avere più utenti che accedono in modo indipendente all'interno di uno stesso sito e di una stessa sessione contemporaneamente. Se per esempio vogliamo avere un'autenticazione separata per l'amministrazione e per la parte pubblica del sito, basta impostare per ciascuna un namespace univoco:

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

È importante ricordarsi di impostare il namespace sempre in tutti i punti che appartengono alla parte in questione. Se usiamo i presenter, impostiamo il namespace nell'antenato comune di quella parte, di solito BasePresenter. Lo facciamo estendendo il metodo [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]:

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

Se cambiate namespace durante una singola richiesta (dopo che lo stato dell'autenticazione è già stato letto), l'oggetto `User` conserva ancora lo stato messo in cache dal namespace precedente. In tal caso chiamate `refreshStorage()` per scartare la cache e forzare una nuova lettura dal nuovo namespace:

```php
$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // ricarica lo stato dal nuovo namespace
```


Più autenticatori
-----------------

Dividere un'applicazione in parti con accesso indipendente richiede di solito anche autenticatori diversi. Se però registrassimo nella configurazione dei servizi due classi che implementano Authenticator, Nette non saprebbe quale assegnare automaticamente all'oggetto `Nette\Security\User` e mostrerebbe un errore. Dobbiamo perciò limitare l'[autowiring |dependency-injection:autowiring] per gli autenticatori, così che funzioni solo quando qualcuno chiede una classe concreta, per esempio `FrontAuthenticator`. Lo si ottiene scegliendo `autowired: self`:

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

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

L'autenticatore dell'oggetto User lo impostiamo prima di chiamare il metodo [login() |api:Nette\Security\User::login()], quindi di solito nel codice del form che esegue l'accesso:

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

Autenticazione degli utenti

Quasi nessuna applicazione web può fare a meno di un meccanismo per far accedere e disconnettere gli utenti e per verificarne i permessi. In questo capitolo parleremo di:

  • accesso e disconnessione degli utenti
  • autenticatori personalizzati

Installazione e requisiti

Negli esempi useremo un oggetto della classe Nette\Security\User, che rappresenta l'utente corrente e che ottenete facendovelo passare con la dependency injection. Nei presenter basta chiamare $user = $this->getUser().

Autenticazione

Autenticazione significa accesso dell'utente, cioè il processo con cui si verifica l'identità di un utente. L'utente si identifica di solito con nome utente e password. La verifica la esegue il cosiddetto Autenticatore. Se l'accesso fallisce, viene lanciata una Nette\Security\AuthenticationException.

try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('Il nome utente o la password inseriti non sono corretti.');
}

L'utente si disconnette così:

$user->logout();

E per sapere se l'utente è connesso:

echo $user->isLoggedIn() ? 'sì' : 'no';

Molto semplice, vero? E di tutti gli aspetti di sicurezza si occupa Nette per voi.

Nei presenter potete verificare l'accesso nel metodo startup() e reindirizzare gli utenti non connessi alla pagina di accesso.

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

Scadenza

L'accesso dell'utente scade insieme alla scadenza dello storage, che di solito è la sessione (vedi l'impostazione della scadenza della sessione). Potete però impostare anche un intervallo di tempo più breve dopo il quale l'utente viene disconnesso. A questo serve il metodo setExpiration(), che si chiama prima di login(). Come argomento passate una stringa con un tempo relativo:

// l'accesso scade dopo 30 minuti di inattività
$user->setExpiration('30 minutes');

// annulla la scadenza impostata
$user->setExpiration(null);

Il metodo $user->getLogoutReason() rivela se l'utente è stato disconnesso perché l'intervallo di tempo è scaduto. Restituisce la costante Nette\Security\User::LogoutInactivity (è scaduto il limite di tempo) oppure User::LogoutManual (è stato chiamato il metodo logout()).

Autenticatore

È un oggetto che verifica le credenziali di accesso, tipicamente nome utente e password. Una forma banale è la classe Nette\Security\SimpleAuthenticator, che si può definire nella configurazione:

security:
	users:
		# nome utente: password
		johndoe: 'secret123'
		kathy: 'evenmoresecretpassword'

Invece delle password in chiaro potete indicare anche i loro hash; vedi la configurazione.

Questa soluzione è più adatta agli scopi di prova. Vi mostreremo come creare un autenticatore che verifica le credenziali di accesso rispetto a una tabella del database.

Un autenticatore è un oggetto che implementa l'interfaccia Nette\Security\Authenticator con il metodo authenticate(). Il suo compito è restituire un'identità oppure lanciare una Nette\Security\AuthenticationException. Si potrebbe anche indicare un codice di errore per distinguere la situazione in modo più fine: Authenticator::IdentityNotFound oppure 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('Utente non trovato.');
		}

		if (!$this->passwords->verify($password, $row->password)) {
			throw new Nette\Security\AuthenticationException('Password non valida.');
		}

		return new SimpleIdentity(
			$row->id,
			$row->role, // oppure un array di ruoli
			['name' => $row->username],
		);
	}
}

La classe MyAuthenticator comunica con il database tramite Nette Database Explorer e lavora con la tabella users, dove la colonna username contiene il nome di accesso dell'utente e la colonna password l'hash della password. Dopo aver verificato nome e password restituisce l'identità, che contiene l'ID dell'utente, il suo ruolo (la colonna role nella tabella), di cui parleremo più avanti, e un array con altri dati (nel nostro caso il nome utente).

L'autenticatore lo aggiungiamo alla configurazione come servizio del container DI:

services:
	- MyAuthenticator

Eventi $onLoggedIn, $onLoggedOut

L'oggetto Nette\Security\User ha gli eventi $onLoggedIn e $onLoggedOut, quindi potete aggiungere callback che si attivano rispettivamente dopo un accesso riuscito o dopo la disconnessione dell'utente.

$user->onLoggedIn[] = function () {
	// l'utente ha appena effettuato l'accesso
};

Identità

L'identità è un insieme di informazioni sull'utente restituito dall'autenticatore, che viene poi conservato nella sessione e si può ottenere con $user->getIdentity(). Possiamo così ottenere l'ID, i ruoli e altri dati dell'utente, proprio come li abbiamo passati nell'autenticatore:

$user->getIdentity()->getId();
// funziona anche la scorciatoia $user->getId()

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

// i dati dell'utente sono accessibili come proprietà
// il nome utente che abbiamo passato in MyAuthenticator
$user->getIdentity()->name;

Cosa importante: alla disconnessione con $user->logout() l'identità non viene cancellata e resta disponibile. Quindi, anche se un utente ha un'identità, non deve per forza essere connesso. Se vogliamo cancellare esplicitamente l'identità, disconnettiamo l'utente chiamando logout(true).

Grazie a questo potete continuare a supporre quale utente sia al computer e mostrargli per esempio offerte personalizzate in un e-shop, ma le sue informazioni personali le potete mostrare solo dopo che ha effettuato l'accesso.

Oltre a cancellare l'identità di volta in volta con logout(true), potete disattivarne del tutto la conservazione con la proprietà $persistIdentity. Impostata a false, l'identità viene scartata a ogni disconnessione e alla scadenza, quindi getIdentity() restituisce poi null. La conservazione dell'identità dipende anche dallo storage: lo storage su cookie non può conservarla dopo la disconnessione, perché cancella sempre il cookie.

L'identità è un oggetto che implementa l'interfaccia Nette\Security\IIdentity. L'implementazione predefinita è Nette\Security\SimpleIdentity. E, come detto, viene mantenuta nella sessione, quindi se per esempio cambiamo il ruolo di uno degli utenti connessi, i vecchi dati resteranno nella sua identità finché non effettuerà di nuovo l'accesso.

Storage dell'utente connesso

Le due informazioni fondamentali sull'utente, cioè se è connesso e la sua identità, vengono di solito trasmesse nella sessione. Il che si può cambiare. Della conservazione di queste informazioni si occupa un oggetto che implementa l'interfaccia Nette\Security\UserStorage. Sono disponibili due implementazioni standard: Nette\Bridges\SecurityHttp\SessionStorage, che trasmette i dati nella sessione, e CookieStorage, che li trasmette in un cookie. Lo storage lo potete scegliere e configurare molto comodamente nella configurazione security › authentication.

Potete inoltre influire su come avvengono esattamente il salvataggio (sleep) e il ripristino (wakeup) dell'identità. Basta che l'autenticatore implementi l'interfaccia Nette\Security\IdentityHandler. Il metodo sleepIdentity() viene chiamato prima che l'identità venga scritta nello storage e wakeupIdentity() dopo che è stata letta. Questi metodi possono modificare il contenuto dell'identità, oppure sostituirla con un nuovo oggetto che restituiscono. Il metodo wakeupIdentity() può perfino restituire null, il che disconnette l'utente. L'interfaccia dichiara anche il metodo getGuestIdentity(), vedi Identità ospite.

Come esempio mostriamo la soluzione alla domanda frequente su come aggiornare i ruoli nell'identità subito dopo il caricamento dalla sessione. Nel metodo wakeupIdentity() passiamo nell'identità i ruoli attuali, per esempio da un database:

final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// qui potete modificare l'identità prima di scriverla nello storage dopo l'accesso,
		// ma ora non ci serve
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// aggiorniamo i ruoli nell'identità
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}

	public function getGuestIdentity(): ?IIdentity
	{
		// qui non si usa alcuna identità ospite
		return null;
	}

Torniamo ora allo storage basato sui cookie. Permette di creare un sito in cui gli utenti possono accedere senza aver bisogno delle sessioni. Non deve quindi scrivere su disco. È così che funziona il sito che state leggendo, forum compreso. In questo caso l'implementazione di IdentityHandler è indispensabile. Nel cookie salveremo solo un token casuale che rappresenta l'utente connesso.

Per prima cosa impostate nella configurazione lo storage necessario con security › authentication › storage: cookie.

Nel database create la colonna authtoken, in cui ogni utente avrà una stringa del tutto casuale, univoca e non indovinabile di lunghezza sufficiente (almeno 13 caratteri). Il CookieStorage trasmette nel cookie solo il valore $identity->getId(), quindi in sleepIdentity() sostituiamo l'identità originale con un'identità proxy che contiene l'authtoken nell'ID. Al contrario, nel metodo wakeupIdentity() leggiamo dal database l'intera identità in base all'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);
		// verifichiamo la password
		// ...
		// restituiamo l'identità con tutti i dati dal database
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// restituiamo un'identità proxy in cui l'ID contiene l'authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// sostituiamo l'identità proxy con quella completa, come in 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
	{
		// qui non si usa alcuna identità ospite
		return null;
	}
}

Identità ospite

A volte torna comodo che anche i visitatori non connessi abbiano un'identità, per esempio per dare loro un insieme predefinito di ruoli o qualche dato. Se l'autenticatore implementa IdentityHandler, può fornirne una con il metodo getGuestIdentity(), che viene usato ogni volta che nessuno è connesso. Allora getIdentity(), getId() e getRoles() ripiegano su di essa, così gli ospiti possono avere ruoli propri invece del semplice ruolo guest. Restituite null se non volete un'identità ospite.

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

L'identità ospite non viene mai salvata nello storage e l'accesso la sostituisce sempre.

Più accessi indipendenti

È possibile avere più utenti che accedono in modo indipendente all'interno di uno stesso sito e di una stessa sessione contemporaneamente. Se per esempio vogliamo avere un'autenticazione separata per l'amministrazione e per la parte pubblica del sito, basta impostare per ciascuna un namespace univoco:

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

È importante ricordarsi di impostare il namespace sempre in tutti i punti che appartengono alla parte in questione. Se usiamo i presenter, impostiamo il namespace nell'antenato comune di quella parte, di solito BasePresenter. Lo facciamo estendendo il metodo checkRequirements():

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

Se cambiate namespace durante una singola richiesta (dopo che lo stato dell'autenticazione è già stato letto), l'oggetto User conserva ancora lo stato messo in cache dal namespace precedente. In tal caso chiamate refreshStorage() per scartare la cache e forzare una nuova lettura dal nuovo namespace:

$user->getStorage()->setNamespace('admin');
$user->refreshStorage(); // ricarica lo stato dal nuovo namespace

Più autenticatori

Dividere un'applicazione in parti con accesso indipendente richiede di solito anche autenticatori diversi. Se però registrassimo nella configurazione dei servizi due classi che implementano Authenticator, Nette non saprebbe quale assegnare automaticamente all'oggetto Nette\Security\User e mostrerebbe un errore. Dobbiamo perciò limitare l'autowiring per gli autenticatori, così che funzioni solo quando qualcuno chiede una classe concreta, per esempio FrontAuthenticator. Lo si ottiene scegliendo autowired: self:

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

L'autenticatore dell'oggetto User lo impostiamo prima di chiamare il metodo login(), quindi di solito nel codice del form che esegue l'accesso:

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