Nette Documentation Preview

syntax
Login de usuários (Autenticação)
********************************

<div class=perex>

Quase nenhuma aplicação web dispensa um mecanismo de login de usuários e verificação de permissões de usuário. Neste capítulo, falaremos sobre:

- login e logout de usuários
- autenticadores personalizados

</div>

→ [Instalação e requisitos |@home#Instalação]

Nos exemplos, usaremos o objeto da classe [api:Nette\Security\User], que representa o usuário atual e ao qual você pode acessar solicitando-o através de [injeção de dependência |dependency-injection:passing-dependencies]. Nos presenters, basta chamar `$user = $this->getUser()`.


Autenticação
============

Autenticação significa **login de usuários**, ou seja, o processo pelo qual se verifica se o usuário é realmente quem ele diz ser. Geralmente, ele se comprova com nome de usuário e senha. A verificação é realizada pelo chamado [##autenticador]. Se o login falhar, uma `Nette\Security\AuthenticationException` é lançada.

```php
try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('Nome de usuário ou senha incorretos');
}
```

Desta forma, você faz logout do usuário:

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

E para verificar se ele está logado:

```php
echo $user->isLoggedIn() ? 'sim' : 'não';
```

Muito simples, não é? E todos os aspectos de segurança são tratados pelo Nette para você.

Nos presenters, você pode verificar o login no método `startup()` e redirecionar o usuário não logado para a página de login.

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


Expiração
=========

O login do usuário expira junto com a [expiração do armazenamento |#Armazenamento do usuário logado], que geralmente é a sessão (veja a configuração de [expiração da sessão |http:configuration#Sessão]). No entanto, também é possível definir um intervalo de tempo menor, após o qual o usuário será desconectado. Para isso, serve o método `setExpiration()`, que é chamado antes de `login()`. Como parâmetro, forneça uma string com tempo relativo:

```php
// o login expira após 30 minutos de inatividade
$user->setExpiration('30 minutes');

// cancelamento da expiração definida
$user->setExpiration(null);
```

Se o usuário foi desconectado devido à expiração do intervalo de tempo, o método `$user->getLogoutReason()` informa, retornando a constante `Nette\Security\UserStorage::LogoutInactivity` (limite de tempo expirado) ou `UserStorage::LogoutManual` (desconectado pelo método `logout()`).


Autenticador
============

É um objeto que verifica as credenciais de login, ou seja, geralmente nome e senha. Uma forma trivial é a classe [api:Nette\Security\SimpleAuthenticator], que podemos definir na [configuração|configuration]:

```neon
security:
	users:
		# nome: senha
		frantisek: tajneheslo
		katka: jestetajnejsiheslo
```

Esta solução é mais adequada para fins de teste. Mostraremos como criar um autenticador que verificará as credenciais de login em uma tabela do banco de dados.

O autenticador é um objeto que implementa a interface [api:Nette\Security\Authenticator] com o método `authenticate()`. Sua tarefa é retornar a chamada [#identidade] ou lançar uma exceção `Nette\Security\AuthenticationException`. Seria possível ainda indicar um código de erro para distinguir mais finamente a situação ocorrida: `Authenticator::IdentityNotFound` e `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, // ou um array de múltiplos papéis
			['name' => $row->username],
		);
	}
}
```

A classe MyAuthenticator comunica com o banco de dados através do [Nette Database Explorer|database:explorer] e trabalha com a tabela `users`, onde na coluna `username` está o nome de login do usuário e na coluna `password` está o [hash da senha|passwords]. Após verificar o nome e a senha, retorna a identidade, que carrega o ID do usuário, seu papel (coluna `role` na tabela), sobre o qual falaremos mais [posteriormente |authorization#Papéis], e um array com outros dados (no nosso caso, o nome de usuário).

Adicionaremos ainda o autenticador à configuração [como um serviço|dependency-injection:services] do contêiner DI:

```neon
services:
	- MyAuthenticator
```


Eventos $onLoggedIn, $onLoggedOut
---------------------------------

O objeto `Nette\Security\User` tem [eventos |nette:glossary#Eventos] `$onLoggedIn` e `$onLoggedOut`, então você pode adicionar callbacks que são chamados após o login bem-sucedido ou após o logout do usuário.


```php
$user->onLoggedIn[] = function () {
	// o usuário acabou de fazer login
};
```


Identidade
==========

A identidade representa um conjunto de informações sobre o usuário, que é retornado pelo autenticador e que é subsequentemente armazenado na sessão e obtido usando `$user->getIdentity()`. Podemos, portanto, obter o id, papéis e outros dados do usuário, da forma como os passamos no autenticador:

```php
$user->getIdentity()->getId();
// o atalho $user->getId() também funciona;

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

// os dados do usuário estão disponíveis como propriedades
// nome que passamos em MyAuthenticator
$user->getIdentity()->name;
```

O que é importante é que, ao fazer logout usando `$user->logout()`, **a identidade não é apagada** e continua disponível. Portanto, embora o usuário tenha uma identidade, ele pode não estar logado. Se quiséssemos apagar explicitamente a identidade, faríamos logout do usuário chamando `logout(true)`.

Graças a isso, você pode continuar a presumir qual usuário está no computador e, por exemplo, exibir ofertas personalizadas em uma loja online, mas só pode exibir seus dados pessoais após o login.

A identidade é um objeto que implementa a interface [api:Nette\Security\IIdentity], a implementação padrão é [api:Nette\Security\SimpleIdentity]. E como mencionado, ela é mantida na sessão, então se, por exemplo, alterarmos o papel de algum dos usuários logados, os dados antigos permanecerão em sua identidade até que ele faça login novamente.


Armazenamento do usuário logado
===============================

Duas informações básicas sobre o usuário, ou seja, se ele está logado e sua [#identidade], geralmente são transmitidas na sessão. O que pode ser alterado. O armazenamento dessas informações é responsabilidade de um objeto que implementa a interface `Nette\Security\UserStorage`. Existem duas implementações padrão disponíveis, a primeira transmite dados na sessão e a segunda em um cookie. São as classes `Nette\Bridges\SecurityHttp\SessionStorage` e `CookieStorage`. Você pode escolher o armazenamento e configurá-lo muito convenientemente na configuração [security › authentication |configuration#Armazenamento].

Além disso, você pode influenciar como exatamente o armazenamento da identidade ocorrerá (*sleep*) e a restauração (*wakeup*). Basta que o autenticador implemente a interface `Nette\Security\IdentityHandler`. Ela tem dois métodos: `sleepIdentity()` é chamado antes de escrever a identidade no armazenamento e `wakeupIdentity()` após sua leitura. Os métodos podem modificar o conteúdo da identidade ou substituí-la por um novo objeto que retornam. O método `wakeupIdentity()` pode até retornar `null`, o que desconecta o usuário.

Como exemplo, mostraremos a solução para a questão frequente de como atualizar os papéis na identidade logo após carregá-la da sessão. No método `wakeupIdentity()`, passaremos para a identidade os papéis atuais, por exemplo, do banco de dados:

```php
final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// aqui é possível modificar a identidade antes de escrever no armazenamento após o login,
		// mas não precisamos disso agora
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// atualização dos papéis na identidade
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}
```

E agora voltaremos ao armazenamento baseado em cookies. Ele permite criar um site onde os usuários podem fazer login sem precisar de sessões. Ou seja, não precisa escrever no disco. Aliás, é assim que funciona o site que você está lendo agora, incluindo o fórum. Neste caso, a implementação de `IdentityHandler` é uma necessidade. No cookie, armazenaremos apenas um token aleatório representando o usuário logado.

Primeiro, na configuração, definiremos o armazenamento desejado usando `security › authentication › storage: cookie`.

No banco de dados, criaremos uma coluna `authtoken`, na qual cada usuário terá uma string [completamente aleatória, única e imprevisível|utils:random] de comprimento suficiente (pelo menos 13 caracteres). O armazenamento `CookieStorage` transmite no cookie apenas o valor `$identity->getId()`, então em `sleepIdentity()` substituiremos a identidade original por uma substituta com `authtoken` no ID, enquanto no método `wakeupIdentity()`, de acordo com o authtoken, leremos a identidade completa do banco de dados:

```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);
		// verificamos a senha
		...
		// retornamos a identidade com todos os dados do banco de dados
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// retornamos a identidade substituta, onde no ID estará o authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// substituímos a identidade substituta pela identidade completa, como em authenticate()
		$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row)
			: null;
	}
}
```


Múltiplos logins independentes
==============================

É possível ter vários usuários logados independentemente dentro de um único site e uma única sessão simultaneamente. Se, por exemplo, quisermos ter autenticação separada para a administração e a parte pública do site, basta definir um nome próprio para cada uma delas:

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

É importante lembrar de definir o namespace sempre em todos os lugares pertencentes à parte correspondente. Se usarmos presenters, definiremos o namespace no ancestral comum para a parte correspondente - geralmente BasePresenter. Faremos isso estendendo o método [checkRequirements() |api:Nette\Application\UI\Presenter::checkRequirements()]:

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


Múltiplos autenticadores
------------------------

A divisão da aplicação em partes com login independente geralmente requer também autenticadores diferentes. No entanto, se registrássemos duas classes implementando Authenticator na configuração de serviços, o Nette não saberia qual delas atribuir automaticamente ao objeto `Nette\Security\User` e exibiria um erro. Portanto, precisamos restringir o [autowiring |dependency-injection:autowiring] para os autenticadores para que funcione apenas quando alguém solicitar uma classe específica, por exemplo, FrontAuthenticator, o que conseguimos escolhendo `autowired: self`:

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

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

Definiremos o autenticador do objeto User antes de chamar o método [login() |api:Nette\Security\User::login()], então geralmente no código do formulário que o loga:

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

Login de usuários (Autenticação)

Quase nenhuma aplicação web dispensa um mecanismo de login de usuários e verificação de permissões de usuário. Neste capítulo, falaremos sobre:

  • login e logout de usuários
  • autenticadores personalizados

Instalação e requisitos

Nos exemplos, usaremos o objeto da classe Nette\Security\User, que representa o usuário atual e ao qual você pode acessar solicitando-o através de injeção de dependência. Nos presenters, basta chamar $user = $this->getUser().

Autenticação

Autenticação significa login de usuários, ou seja, o processo pelo qual se verifica se o usuário é realmente quem ele diz ser. Geralmente, ele se comprova com nome de usuário e senha. A verificação é realizada pelo chamado autenticador. Se o login falhar, uma Nette\Security\AuthenticationException é lançada.

try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('Nome de usuário ou senha incorretos');
}

Desta forma, você faz logout do usuário:

$user->logout();

E para verificar se ele está logado:

echo $user->isLoggedIn() ? 'sim' : 'não';

Muito simples, não é? E todos os aspectos de segurança são tratados pelo Nette para você.

Nos presenters, você pode verificar o login no método startup() e redirecionar o usuário não logado para a página de login.

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

Expiração

O login do usuário expira junto com a expiração do armazenamento, que geralmente é a sessão (veja a configuração de expiração da sessão). No entanto, também é possível definir um intervalo de tempo menor, após o qual o usuário será desconectado. Para isso, serve o método setExpiration(), que é chamado antes de login(). Como parâmetro, forneça uma string com tempo relativo:

// o login expira após 30 minutos de inatividade
$user->setExpiration('30 minutes');

// cancelamento da expiração definida
$user->setExpiration(null);

Se o usuário foi desconectado devido à expiração do intervalo de tempo, o método $user->getLogoutReason() informa, retornando a constante Nette\Security\UserStorage::LogoutInactivity (limite de tempo expirado) ou UserStorage::LogoutManual (desconectado pelo método logout()).

Autenticador

É um objeto que verifica as credenciais de login, ou seja, geralmente nome e senha. Uma forma trivial é a classe Nette\Security\SimpleAuthenticator, que podemos definir na configuração:

security:
	users:
		# nome: senha
		frantisek: tajneheslo
		katka: jestetajnejsiheslo

Esta solução é mais adequada para fins de teste. Mostraremos como criar um autenticador que verificará as credenciais de login em uma tabela do banco de dados.

O autenticador é um objeto que implementa a interface Nette\Security\Authenticator com o método authenticate(). Sua tarefa é retornar a chamada identidade ou lançar uma exceção Nette\Security\AuthenticationException. Seria possível ainda indicar um código de erro para distinguir mais finamente a situação ocorrida: Authenticator::IdentityNotFound e 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, // ou um array de múltiplos papéis
			['name' => $row->username],
		);
	}
}

A classe MyAuthenticator comunica com o banco de dados através do Nette Database Explorer e trabalha com a tabela users, onde na coluna username está o nome de login do usuário e na coluna password está o hash da senha. Após verificar o nome e a senha, retorna a identidade, que carrega o ID do usuário, seu papel (coluna role na tabela), sobre o qual falaremos mais posteriormente, e um array com outros dados (no nosso caso, o nome de usuário).

Adicionaremos ainda o autenticador à configuração como um serviço do contêiner DI:

services:
	- MyAuthenticator

Eventos $onLoggedIn, $onLoggedOut

O objeto Nette\Security\User tem eventos $onLoggedIn e $onLoggedOut, então você pode adicionar callbacks que são chamados após o login bem-sucedido ou após o logout do usuário.

$user->onLoggedIn[] = function () {
	// o usuário acabou de fazer login
};

Identidade

A identidade representa um conjunto de informações sobre o usuário, que é retornado pelo autenticador e que é subsequentemente armazenado na sessão e obtido usando $user->getIdentity(). Podemos, portanto, obter o id, papéis e outros dados do usuário, da forma como os passamos no autenticador:

$user->getIdentity()->getId();
// o atalho $user->getId() também funciona;

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

// os dados do usuário estão disponíveis como propriedades
// nome que passamos em MyAuthenticator
$user->getIdentity()->name;

O que é importante é que, ao fazer logout usando $user->logout(), a identidade não é apagada e continua disponível. Portanto, embora o usuário tenha uma identidade, ele pode não estar logado. Se quiséssemos apagar explicitamente a identidade, faríamos logout do usuário chamando logout(true).

Graças a isso, você pode continuar a presumir qual usuário está no computador e, por exemplo, exibir ofertas personalizadas em uma loja online, mas só pode exibir seus dados pessoais após o login.

A identidade é um objeto que implementa a interface Nette\Security\IIdentity, a implementação padrão é Nette\Security\SimpleIdentity. E como mencionado, ela é mantida na sessão, então se, por exemplo, alterarmos o papel de algum dos usuários logados, os dados antigos permanecerão em sua identidade até que ele faça login novamente.

Armazenamento do usuário logado

Duas informações básicas sobre o usuário, ou seja, se ele está logado e sua identidade, geralmente são transmitidas na sessão. O que pode ser alterado. O armazenamento dessas informações é responsabilidade de um objeto que implementa a interface Nette\Security\UserStorage. Existem duas implementações padrão disponíveis, a primeira transmite dados na sessão e a segunda em um cookie. São as classes Nette\Bridges\SecurityHttp\SessionStorage e CookieStorage. Você pode escolher o armazenamento e configurá-lo muito convenientemente na configuração security › authentication.

Além disso, você pode influenciar como exatamente o armazenamento da identidade ocorrerá (sleep) e a restauração (wakeup). Basta que o autenticador implemente a interface Nette\Security\IdentityHandler. Ela tem dois métodos: sleepIdentity() é chamado antes de escrever a identidade no armazenamento e wakeupIdentity() após sua leitura. Os métodos podem modificar o conteúdo da identidade ou substituí-la por um novo objeto que retornam. O método wakeupIdentity() pode até retornar null, o que desconecta o usuário.

Como exemplo, mostraremos a solução para a questão frequente de como atualizar os papéis na identidade logo após carregá-la da sessão. No método wakeupIdentity(), passaremos para a identidade os papéis atuais, por exemplo, do banco de dados:

final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// aqui é possível modificar a identidade antes de escrever no armazenamento após o login,
		// mas não precisamos disso agora
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// atualização dos papéis na identidade
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}

E agora voltaremos ao armazenamento baseado em cookies. Ele permite criar um site onde os usuários podem fazer login sem precisar de sessões. Ou seja, não precisa escrever no disco. Aliás, é assim que funciona o site que você está lendo agora, incluindo o fórum. Neste caso, a implementação de IdentityHandler é uma necessidade. No cookie, armazenaremos apenas um token aleatório representando o usuário logado.

Primeiro, na configuração, definiremos o armazenamento desejado usando security › authentication › storage: cookie.

No banco de dados, criaremos uma coluna authtoken, na qual cada usuário terá uma string completamente aleatória, única e imprevisível de comprimento suficiente (pelo menos 13 caracteres). O armazenamento CookieStorage transmite no cookie apenas o valor $identity->getId(), então em sleepIdentity() substituiremos a identidade original por uma substituta com authtoken no ID, enquanto no método wakeupIdentity(), de acordo com o authtoken, leremos a identidade completa do banco de dados:

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);
		// verificamos a senha
		...
		// retornamos a identidade com todos os dados do banco de dados
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// retornamos a identidade substituta, onde no ID estará o authtoken
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// substituímos a identidade substituta pela identidade completa, como em authenticate()
		$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row)
			: null;
	}
}

Múltiplos logins independentes

É possível ter vários usuários logados independentemente dentro de um único site e uma única sessão simultaneamente. Se, por exemplo, quisermos ter autenticação separada para a administração e a parte pública do site, basta definir um nome próprio para cada uma delas:

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

É importante lembrar de definir o namespace sempre em todos os lugares pertencentes à parte correspondente. Se usarmos presenters, definiremos o namespace no ancestral comum para a parte correspondente – geralmente BasePresenter. Faremos isso estendendo o método checkRequirements():

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

Múltiplos autenticadores

A divisão da aplicação em partes com login independente geralmente requer também autenticadores diferentes. No entanto, se registrássemos duas classes implementando Authenticator na configuração de serviços, o Nette não saberia qual delas atribuir automaticamente ao objeto Nette\Security\User e exibiria um erro. Portanto, precisamos restringir o autowiring para os autenticadores para que funcione apenas quando alguém solicitar uma classe específica, por exemplo, FrontAuthenticator, o que conseguimos escolhendo autowired: self:

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

Definiremos o autenticador do objeto User antes de chamar o método login(), então geralmente no código do formulário que o loga:

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