Nette Documentation Preview

syntax
Autenticación de usuarios
*************************

<div class=perex>

Las aplicaciones web poco o nada necesitan ningún mecanismo para el login de los usuarios o para comprobar sus privilegios. En este capítulo hablaremos de:

- login y logout de usuario
- autenticadores y autorizadores personalizados

</div>

→ [Instalación y requisitos |@home#Installation]

En los ejemplos utilizaremos un objeto de la clase [api:Nette\Security\User], que representa al usuario actual y que se obtiene pasándolo mediante [inyección de dependencia |dependency-injection:passing-dependencies]. En los presentadores basta con llamar a `$user = $this->getUser()`.


Autenticación .[#toc-authentication]
====================================

Autenticación significa **inicio de sesión del usuario**, es decir, el proceso durante el cual se verifica la identidad de un usuario. El usuario suele identificarse mediante un nombre de usuario y una contraseña. La verificación la realiza el llamado [autenticador |#authenticator]. Si el login falla, se lanza `Nette\Security\AuthenticationException`.

```php
try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('The username or password you entered is incorrect.');
}
```

Así se cierra la sesión del usuario:

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

Y comprobar si el usuario está conectado:

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

Sencillo, ¿verdad? Y todos los aspectos de seguridad son manejados por Nette para usted.

En el presentador, puede verificar el inicio de sesión en el método `startup()` y redirigir a un usuario que no haya iniciado sesión a la página de inicio de sesión.

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


Caducidad .[#toc-expiration]
============================

El inicio de sesión del usuario expira junto con [la expiración del repositorio |#Storage for Logged User], que suele ser una sesión (véase el ajuste de [expiración de sesión |http:configuration#session] ).
Sin embargo, también se puede establecer un intervalo de tiempo más corto tras el cual se cierra la sesión del usuario. El método `setExpiration()`, que se llama antes de `login()`, se utiliza para este propósito. Proporcione una cadena con una hora relativa como parámetro:

```php
// el login expira tras 30 minutos de inactividad
$user->setExpiration('30 minutes');

// cancelar la expiración establecida
$user->setExpiration(null);
```

El método `$user->getLogoutReason()` indica si se ha cerrado la sesión del usuario porque ha expirado el intervalo de tiempo. Devuelve la constante `Nette\Security\UserStorage::LogoutInactivity` si el tiempo expiró o `UserStorage::LogoutManual` cuando se llamó al método `logout()`.


Autenticador .[#toc-authenticator]
==================================

Es un objeto que verifica los datos de acceso, es decir, normalmente el nombre y la contraseña. La implementación trivial es la clase [api:Nette\Security\SimpleAuthenticator], que puede definirse en [configuración |configuration]:

```neon
security:
	users:
		# name: password
		johndoe: secret123
		kathy: evenmoresecretpassword
```

Esta solución es más adecuada para realizar pruebas. Le mostraremos cómo crear un autenticador que verificará las credenciales contra una tabla de base de datos.

Un autenticador es un objeto que implementa la interfaz [api:Nette\Security\Authenticator] con el método `authenticate()`. Su tarea es devolver la llamada [identidad |#identity] o lanzar una excepción `Nette\Security\AuthenticationException`. También sería posible proporcionar un código de error de grano fino `Authenticator::IdentityNotFound` o `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('User not found.');
		}

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

		return new SimpleIdentity(
			$row->id,
			$row->role, // o array de roles
			['name' => $row->username],
		);
	}
}
```

La clase MyAuthenticator se comunica con la base de datos a través de [Nette Database Explorer |database:explorer] y trabaja con la tabla `users`, donde la columna `username` contiene el nombre de usuario y la columna `password` contiene el [hash |passwords]. Tras verificar el nombre y la contraseña, devuelve la identidad con el ID del usuario, el rol (columna `role` de la tabla), que mencionaremos [más adelante |#roles], y un array con datos adicionales (en nuestro caso, el nombre de usuario).

Añadiremos el autenticador a la configuración [como un servicio |dependency-injection:services] del contenedor DI:

```neon
services:
	- MyAuthenticator
```


Eventos $onLoggedIn, $onLoggedOut .[#toc-onloggedin-onloggedout-events]
-----------------------------------------------------------------------

El objeto `Nette\Security\User` tiene los [eventos |nette:glossary#Events] `$onLoggedIn` y `$onLoggedOut`, por lo que puedes añadir callbacks que se activen después de un login exitoso o después de que el usuario se desconecte.


```php
$user->onLoggedIn[] = function () {
	// el usuario acaba de iniciar sesión
};
```


Identidad .[#toc-identity]
==========================

Una identidad es un conjunto de información sobre un usuario que devuelve el autenticador y que luego se almacena en una sesión y se recupera utilizando `$user->getIdentity()`. Así podemos obtener el id, roles y otros datos del usuario tal y como los pasamos en el autenticador:

```php
$user->getIdentity()->getId();
// también funciona el atajo $user->getId();

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

// se puede acceder a los datos del usuario como propiedades
// el nombre que pasamos en MyAuthenticator
$user->getIdentity()->name;
```

Es importante destacar que cuando el usuario cierra la sesión utilizando `$user->logout()`, **la identidad no se borra** y sigue estando disponible. Por lo tanto, si la identidad existe, por sí misma no garantiza que el usuario también haya iniciado sesión. Si queremos borrar explícitamente la identidad, cerramos la sesión del usuario mediante `logout(true)`.

Gracias a esto, todavía se puede suponer qué usuario está en el ordenador y, por ejemplo, mostrar ofertas personalizadas en la tienda electrónica, sin embargo, sólo se pueden mostrar sus datos personales después de iniciar sesión.

Identity es un objeto que implementa la interfaz [api:Nette\Security\IIdentity], la implementación por defecto es [api:Nette\Security\SimpleIdentity]. Y como se ha mencionado, la identidad se almacena en la sesión, por lo que si, por ejemplo, cambiamos el rol de alguno de los usuarios logueados, los datos antiguos se mantendrán en la identidad hasta que vuelva a loguearse.


Almacenamiento para el usuario conectado .[#toc-storage-for-logged-user]
========================================================================

Los dos datos básicos sobre el usuario, es decir, si ha iniciado sesión y su [identidad |#identity], se suelen guardar en la sesión. La cual puede ser modificada. Para almacenar esta información es responsable un objeto que implemente la interfaz `Nette\Security\UserStorage`. Existen dos implementaciones estándar, la primera transmite los datos en una sesión y la segunda en una cookie. Estas son las clases `Nette\Bridges\SecurityHttp\SessionStorage` y `CookieStorage`. Usted puede elegir el almacenamiento y configurarlo muy convenientemente en la configuración de [seguridad › autenticación |configuration].

También puedes controlar exactamente cómo se realizará el guardado (*sleep*) y el restablecimiento (*wakeup*) de la identidad. Todo lo que necesitas es que el autenticador implemente la interfaz `Nette\Security\IdentityHandler`. Este tiene dos métodos: `sleepIdentity()` es llamado antes de que la identidad sea escrita en el almacenamiento, y `wakeupIdentity()` es llamado después de que la identidad sea leída. Los métodos pueden modificar el contenido de la identidad, o sustituirla por un nuevo objeto devuelto. El método `wakeupIdentity()` puede incluso devolver `null`, que cierra la sesión del usuario.

Como ejemplo, mostraremos una solución a una pregunta común sobre cómo actualizar los roles de identidad justo después de restaurar desde una sesión. En el método `wakeupIdentity()` pasamos los roles actuales a la identidad, por ejemplo desde la base de datos:

```php
final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// aquí puedes cambiar la identidad antes de almacenar después de iniciar sesión,
		// pero no lo necesitamos ahora
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// actualización de funciones en la identidad
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}
```

Y ahora volvemos al almacenamiento basado en cookies. Permite crear un sitio web en el que los usuarios pueden iniciar sesión sin necesidad de utilizar sesiones. Por lo tanto, no necesita escribir en el disco. Después de todo, así es como funciona el sitio web que estás leyendo ahora, incluido el foro. En este caso, la implementación de `IdentityHandler` es una necesidad. Sólo almacenaremos en la cookie un token aleatorio que representa al usuario logueado.

Así que primero establecemos el almacenamiento deseado en la configuración usando `security › authentication › storage: cookie`.

Añadiremos una columna `authtoken` en la base de datos, en la que cada usuario tendrá una cadena [completamente aleatoria, única e indescifrable|utils:random] de longitud suficiente (al menos 13 caracteres). El repositorio `CookieStorage` almacena sólo el valor `$identity->getId()` en la cookie, así que en `sleepIdentity()` reemplazamos la identidad original con un proxy con `authtoken` en el ID, por el contrario en el método `wakeupIdentity()` restauramos la identidad completa desde la base de datos según 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);
		// comprobar contraseña
		...
		// devolvemos la identidad con todos los datos de la base de datos
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// devolvemos una identidad proxy, donde en el ID es authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// sustituir la identidad proxy por una identidad completa, como en authenticate()
		$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row)
			: null;
	}
}
```


Autenticaciones Múltiples Independientes .[#toc-multiple-independent-authentications]
=====================================================================================

Es posible tener varios usuarios registrados independientes dentro de un mismo sitio y una sesión a la vez. Por ejemplo, si queremos tener una autenticación independiente para el frontend y el backend, simplemente estableceremos un espacio de nombres de sesión único para cada uno de ellos:

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

Es necesario tener en cuenta que esto debe establecerse en todos los sitios que pertenezcan al mismo segmento. Cuando utilicemos presentadores, estableceremos el espacio de nombres en el ancestro común - normalmente el BasePresenter. Para ello extenderemos el método [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]:

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


Autenticadores múltiples .[#toc-multiple-authenticators]
--------------------------------------------------------

Dividir una aplicación en segmentos con autenticación independiente generalmente requiere diferentes autenticadores. Sin embargo, registrar dos clases que implementan Authenticator en config services provocaría un error porque Nette no sabría cuál de ellas debería [autocablearse |dependency-injection:autowiring] al objeto `Nette\Security\User`. Por eso debemos limitar el autocableado para ellos con `autowired: self` de forma que se active sólo cuando se solicite específicamente su clase:

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

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

Sólo necesitamos establecer nuestro autenticador al objeto User antes de llamar al método [login() |api:Nette\Security\User::login()] lo que típicamente significa en el callback del formulario de login:

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

Autenticación de usuarios

Las aplicaciones web poco o nada necesitan ningún mecanismo para el login de los usuarios o para comprobar sus privilegios. En este capítulo hablaremos de:

  • login y logout de usuario
  • autenticadores y autorizadores personalizados

Instalación y requisitos

En los ejemplos utilizaremos un objeto de la clase Nette\Security\User, que representa al usuario actual y que se obtiene pasándolo mediante inyección de dependencia. En los presentadores basta con llamar a $user = $this->getUser().

Autenticación

Autenticación significa inicio de sesión del usuario, es decir, el proceso durante el cual se verifica la identidad de un usuario. El usuario suele identificarse mediante un nombre de usuario y una contraseña. La verificación la realiza el llamado autenticador. Si el login falla, se lanza Nette\Security\AuthenticationException.

try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('The username or password you entered is incorrect.');
}

Así se cierra la sesión del usuario:

$user->logout();

Y comprobar si el usuario está conectado:

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

Sencillo, ¿verdad? Y todos los aspectos de seguridad son manejados por Nette para usted.

En el presentador, puede verificar el inicio de sesión en el método startup() y redirigir a un usuario que no haya iniciado sesión a la página de inicio de sesión.

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

Caducidad

El inicio de sesión del usuario expira junto con la expiración del repositorio, que suele ser una sesión (véase el ajuste de expiración de sesión ). Sin embargo, también se puede establecer un intervalo de tiempo más corto tras el cual se cierra la sesión del usuario. El método setExpiration(), que se llama antes de login(), se utiliza para este propósito. Proporcione una cadena con una hora relativa como parámetro:

// el login expira tras 30 minutos de inactividad
$user->setExpiration('30 minutes');

// cancelar la expiración establecida
$user->setExpiration(null);

El método $user->getLogoutReason() indica si se ha cerrado la sesión del usuario porque ha expirado el intervalo de tiempo. Devuelve la constante Nette\Security\UserStorage::LogoutInactivity si el tiempo expiró o UserStorage::LogoutManual cuando se llamó al método logout().

Autenticador

Es un objeto que verifica los datos de acceso, es decir, normalmente el nombre y la contraseña. La implementación trivial es la clase Nette\Security\SimpleAuthenticator, que puede definirse en configuración:

security:
	users:
		# name: password
		johndoe: secret123
		kathy: evenmoresecretpassword

Esta solución es más adecuada para realizar pruebas. Le mostraremos cómo crear un autenticador que verificará las credenciales contra una tabla de base de datos.

Un autenticador es un objeto que implementa la interfaz Nette\Security\Authenticator con el método authenticate(). Su tarea es devolver la llamada identidad o lanzar una excepción Nette\Security\AuthenticationException. También sería posible proporcionar un código de error de grano fino Authenticator::IdentityNotFound o 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('User not found.');
		}

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

		return new SimpleIdentity(
			$row->id,
			$row->role, // o array de roles
			['name' => $row->username],
		);
	}
}

La clase MyAuthenticator se comunica con la base de datos a través de Nette Database Explorer y trabaja con la tabla users, donde la columna username contiene el nombre de usuario y la columna password contiene el hash. Tras verificar el nombre y la contraseña, devuelve la identidad con el ID del usuario, el rol (columna role de la tabla), que mencionaremos más adelante, y un array con datos adicionales (en nuestro caso, el nombre de usuario).

Añadiremos el autenticador a la configuración como un servicio del contenedor DI:

services:
	- MyAuthenticator

Eventos $onLoggedIn, $onLoggedOut

El objeto Nette\Security\User tiene los eventos $onLoggedIn y $onLoggedOut, por lo que puedes añadir callbacks que se activen después de un login exitoso o después de que el usuario se desconecte.

$user->onLoggedIn[] = function () {
	// el usuario acaba de iniciar sesión
};

Identidad

Una identidad es un conjunto de información sobre un usuario que devuelve el autenticador y que luego se almacena en una sesión y se recupera utilizando $user->getIdentity(). Así podemos obtener el id, roles y otros datos del usuario tal y como los pasamos en el autenticador:

$user->getIdentity()->getId();
// también funciona el atajo $user->getId();

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

// se puede acceder a los datos del usuario como propiedades
// el nombre que pasamos en MyAuthenticator
$user->getIdentity()->name;

Es importante destacar que cuando el usuario cierra la sesión utilizando $user->logout(), la identidad no se borra y sigue estando disponible. Por lo tanto, si la identidad existe, por sí misma no garantiza que el usuario también haya iniciado sesión. Si queremos borrar explícitamente la identidad, cerramos la sesión del usuario mediante logout(true).

Gracias a esto, todavía se puede suponer qué usuario está en el ordenador y, por ejemplo, mostrar ofertas personalizadas en la tienda electrónica, sin embargo, sólo se pueden mostrar sus datos personales después de iniciar sesión.

Identity es un objeto que implementa la interfaz Nette\Security\IIdentity, la implementación por defecto es Nette\Security\SimpleIdentity. Y como se ha mencionado, la identidad se almacena en la sesión, por lo que si, por ejemplo, cambiamos el rol de alguno de los usuarios logueados, los datos antiguos se mantendrán en la identidad hasta que vuelva a loguearse.

Almacenamiento para el usuario conectado

Los dos datos básicos sobre el usuario, es decir, si ha iniciado sesión y su identidad, se suelen guardar en la sesión. La cual puede ser modificada. Para almacenar esta información es responsable un objeto que implemente la interfaz Nette\Security\UserStorage. Existen dos implementaciones estándar, la primera transmite los datos en una sesión y la segunda en una cookie. Estas son las clases Nette\Bridges\SecurityHttp\SessionStorage y CookieStorage. Usted puede elegir el almacenamiento y configurarlo muy convenientemente en la configuración de seguridad › autenticación.

También puedes controlar exactamente cómo se realizará el guardado (sleep) y el restablecimiento (wakeup) de la identidad. Todo lo que necesitas es que el autenticador implemente la interfaz Nette\Security\IdentityHandler. Este tiene dos métodos: sleepIdentity() es llamado antes de que la identidad sea escrita en el almacenamiento, y wakeupIdentity() es llamado después de que la identidad sea leída. Los métodos pueden modificar el contenido de la identidad, o sustituirla por un nuevo objeto devuelto. El método wakeupIdentity() puede incluso devolver null, que cierra la sesión del usuario.

Como ejemplo, mostraremos una solución a una pregunta común sobre cómo actualizar los roles de identidad justo después de restaurar desde una sesión. En el método wakeupIdentity() pasamos los roles actuales a la identidad, por ejemplo desde la base de datos:

final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// aquí puedes cambiar la identidad antes de almacenar después de iniciar sesión,
		// pero no lo necesitamos ahora
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// actualización de funciones en la identidad
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}

Y ahora volvemos al almacenamiento basado en cookies. Permite crear un sitio web en el que los usuarios pueden iniciar sesión sin necesidad de utilizar sesiones. Por lo tanto, no necesita escribir en el disco. Después de todo, así es como funciona el sitio web que estás leyendo ahora, incluido el foro. En este caso, la implementación de IdentityHandler es una necesidad. Sólo almacenaremos en la cookie un token aleatorio que representa al usuario logueado.

Así que primero establecemos el almacenamiento deseado en la configuración usando security › authentication › storage: cookie.

Añadiremos una columna authtoken en la base de datos, en la que cada usuario tendrá una cadena completamente aleatoria, única e indescifrable de longitud suficiente (al menos 13 caracteres). El repositorio CookieStorage almacena sólo el valor $identity->getId() en la cookie, así que en sleepIdentity() reemplazamos la identidad original con un proxy con authtoken en el ID, por el contrario en el método wakeupIdentity() restauramos la identidad completa desde la base de datos según 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);
		// comprobar contraseña
		...
		// devolvemos la identidad con todos los datos de la base de datos
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// devolvemos una identidad proxy, donde en el ID es authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// sustituir la identidad proxy por una identidad completa, como en authenticate()
		$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row)
			: null;
	}
}

Autenticaciones Múltiples Independientes

Es posible tener varios usuarios registrados independientes dentro de un mismo sitio y una sesión a la vez. Por ejemplo, si queremos tener una autenticación independiente para el frontend y el backend, simplemente estableceremos un espacio de nombres de sesión único para cada uno de ellos:

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

Es necesario tener en cuenta que esto debe establecerse en todos los sitios que pertenezcan al mismo segmento. Cuando utilicemos presentadores, estableceremos el espacio de nombres en el ancestro común – normalmente el BasePresenter. Para ello extenderemos el método checkRequirements():

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

Autenticadores múltiples

Dividir una aplicación en segmentos con autenticación independiente generalmente requiere diferentes autenticadores. Sin embargo, registrar dos clases que implementan Authenticator en config services provocaría un error porque Nette no sabría cuál de ellas debería autocablearse al objeto Nette\Security\User. Por eso debemos limitar el autocableado para ellos con autowired: self de forma que se active sólo cuando se solicite específicamente su clase:

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

Sólo necesitamos establecer nuestro autenticador al objeto User antes de llamar al método login() lo que típicamente significa en el callback del formulario de login:

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