Autenticación

Nette proporciona una forma de implementar la autenticación en nuestras páginas, pero no nos obliga a nada. La implementación depende únicamente de nosotros. Nette contiene la interfaz Nette\Security\Authenticator, que requiere solo un método authenticate, que verifica al usuario de la manera que queramos.

Hay muchas opciones sobre cómo se puede verificar a un usuario. La forma más común de verificación es mediante contraseña (el usuario proporciona su nombre o correo electrónico y contraseña), pero existen otras formas. Quizás conozca los botones tipo “Iniciar sesión con Facebook” o el inicio de sesión con Google/Twitter/GitHub en algunas páginas. Con Nette podemos tener cualquier método de inicio de sesión, o incluso podemos combinarlos. Depende solo de nosotros.

Normalmente escribiríamos nuestro propio autenticador, pero para este pequeño blog simple usaremos el autenticador incorporado, que inicia sesión basándose en la contraseña y el nombre de usuario almacenados en el archivo de configuración. Es útil para fines de prueba. Por lo tanto, agregaremos la siguiente sección security al archivo de configuración config/common.neon:

security:
	users:
		admin: secret  # user 'admin', password 'secret'

Nette creará automáticamente un servicio en el contenedor DI.

Formulario de inicio de sesión

Ahora tenemos la autenticación preparada y debemos preparar la interfaz de usuario para el inicio de sesión. Creemos un nuevo presenter llamado SignPresenter, que:

  • mostrará el formulario de inicio de sesión (con nombre de usuario y contraseña)
  • después de enviar el formulario, verificará al usuario
  • proporcionará la opción de cerrar sesión

Comenzaremos con el formulario de inicio de sesión. Ya sabemos cómo funcionan los formularios en los presenters. Crearemos el presenter SignPresenter y escribiremos el método createComponentSignInForm. Debería verse algo así:

<?php
namespace App\Presentation\Sign;

use Nette;
use Nette\Application\UI\Form;

final class SignPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentSignInForm(): Form
	{
		$form = new Form;
		$form->addText('username', 'Nombre de usuario:')
			->setRequired('Por favor, introduzca su nombre de usuario.');

		$form->addPassword('password', 'Contraseña:')
			->setRequired('Por favor introduzca su contraseña.');

		$form->addSubmit('send', 'Iniciar sesión');

		$form->onSuccess[] = $this->signInFormSucceeded(...);
		return $form;
	}
}

Hay campos para el nombre de usuario y la contraseña.

Plantilla

El formulario se renderizará en la plantilla in.latte:

{block content}
<h1 n:block=title>Iniciar sesión</h1>

{control signInForm}

Callback de inicio de sesión

A continuación, agregaremos el callback para iniciar sesión del usuario, que se llamará inmediatamente después de enviar el formulario con éxito.

El callback simplemente tomará el nombre de usuario y la contraseña que el usuario completó y los pasará al autenticador. Después de iniciar sesión, redirigiremos a la página de inicio.

private function signInFormSucceeded(Form $form, \stdClass $data): void
{
	try {
		$this->getUser()->login($data->username, $data->password);
		$this->redirect('Home:');

	} catch (Nette\Security\AuthenticationException $e) {
		$form->addError('Nombre de usuario o contraseña incorrectos.');
	}
}

El método User::login() lanzará una excepción si el nombre de usuario y la contraseña no coinciden con los datos del archivo de configuración. Como ya sabemos, esto puede resultar en una página de error roja, o en modo de producción en un mensaje informando sobre un error del servidor. Sin embargo, no queremos eso. Por lo tanto, capturaremos esta excepción y pasaremos un mensaje de error agradable y fácil de usar al formulario.

Tan pronto como ocurra un error en el formulario, la página con el formulario se volverá a dibujar y encima del formulario se mostrará un mensaje agradable informando al usuario que ingresó un nombre de usuario o contraseña incorrectos.

Asegurando los presenters

Aseguraremos el formulario para agregar y editar publicaciones. Este está definido en el presenter EditPresenter. El objetivo es impedir el acceso a la página a los usuarios que no han iniciado sesión.

Crearemos el método startup(), que se ejecuta inmediatamente al principio del ciclo de vida del presenter. Este redirigirá a los usuarios no autenticados al formulario de inicio de sesión.

public function startup(): void
{
	parent::startup();

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

Ocultar enlaces

Un usuario no autorizado ya no puede ver la página create ni edit, pero aún puede ver los enlaces a ellas. Deberíamos ocultarlos también. Uno de esos enlaces está en la plantilla app/Presentation/Home/default.latte y solo deberían verlo los usuarios que hayan iniciado sesión.

Podemos ocultarlo usando un n:atributo llamado n:if. Si esta condición es false, toda la etiqueta <a>, incluido el contenido, permanecerá oculta.

<a n:href="Edit:create" n:if="$user->isLoggedIn()">Crear publicación</a>

que es una abreviatura de la siguiente notación (no confundir con tag-if):

{if $user->isLoggedIn()}<a n:href="Edit:create">Crear publicación</a>{/if}

De la misma manera, ocultaremos también el enlace en la plantilla app/Presentation/Post/show.latte.

Enlace de inicio de sesión

¿Cómo llegamos realmente a la página de inicio de sesión? No hay ningún enlace que conduzca a ella. Así que agreguémoslo a la plantilla @layout.latte. Intente encontrar un lugar adecuado, puede estar casi en cualquier lugar.

...
<ul class="navig">
	<li><a n:href="Home:">Artículos</a></li>
	{if $user->isLoggedIn()}
		<li><a n:href="Sign:out">Cerrar sesión</a></li>
	{else}
		<li><a n:href="Sign:in">Iniciar sesión</a></li>
	{/if}
</ul>
...

Si el usuario no ha iniciado sesión, se mostrará el enlace “Iniciar sesión”. En caso contrario, se mostrará el enlace “Cerrar sesión”. También agregaremos esta acción a SignPresenter.

Dado que redirigimos al usuario inmediatamente después de cerrar la sesión, no se necesita ninguna plantilla. El cierre de sesión se ve así:

public function actionOut(): void
{
	$this->getUser()->logout();
	$this->flashMessage('El cierre de sesión fue exitoso.');
	$this->redirect('Home:');
}

Simplemente se llama al método logout() y luego se muestra un mensaje agradable confirmando el cierre de sesión exitoso.

Resumen

Tenemos un enlace para iniciar sesión y también para cerrar sesión del usuario. Para la verificación, utilizamos el autenticador incorporado y tenemos los datos de inicio de sesión en el archivo de configuración, ya que se trata de una aplicación de prueba simple. También hemos asegurado los formularios de edición, por lo que solo los usuarios que hayan iniciado sesión pueden agregar y editar publicaciones.

Aquí puede leer más sobre el inicio de sesión de usuarios y la Verificación de permisos.