Authentification

Nette vous fournit des lignes directrices sur la façon de programmer l'authentification sur votre page, mais il ne vous oblige pas à le faire d'une manière particulière. L'implémentation dépend de vous. Nette a une interface Nette\Security\Authenticator qui vous oblige à implémenter une seule méthode appelée authenticate, qui trouve l'utilisateur comme vous le souhaitez.

Il existe de nombreuses façons pour un utilisateur de s'authentifier. La méthode la plus courante est l'authentification par mot de passe (l'utilisateur fournit son nom ou son adresse électronique et un mot de passe), mais il existe d'autres moyens. Vous connaissez peut-être les boutons “Connexion avec Facebook” sur de nombreux sites web, ou la connexion via Google/Twitter/GitHub ou tout autre site. Avec Nette, vous pouvez avoir la méthode d'authentification que vous voulez, ou vous pouvez les combiner. C'est vous qui décidez.

Normalement, vous devriez écrire votre propre authentificateur, mais pour ce petit blog simple, nous utiliserons l'authentificateur intégré, qui authentifie sur la base d'un mot de passe et d'un nom d'utilisateur stockés dans un fichier de configuration. C'est une bonne solution pour les tests. Nous allons donc ajouter la section security suivante au fichier de configuration de config/common.neon:

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

Nette va automatiquement créer un service dans le conteneur DI.

Formulaire d'inscription

Nous avons maintenant la partie backend de l'authentification prête et nous devons fournir une interface utilisateur, par laquelle l'utilisateur se connectera. Créons un nouveau présentateur appelé SignPresenter, qui va

  • afficher un formulaire de connexion (demandant le nom d'utilisateur et le mot de passe)
  • authentifier l'utilisateur lorsque le formulaire est soumis
  • fournir une action de déconnexion

Commençons par le formulaire de connexion. Vous savez déjà comment fonctionnent les formulaires dans un présentateur. Créez le site SignPresenter et la méthode createComponentSignInForm. Cela devrait ressembler à ceci :

<?php
namespace App\UI\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', 'Username:')
			->setRequired('Please enter your username.');

		$form->addPassword('password', 'Password:')
			->setRequired('Please enter your password.');

		$form->addSubmit('send', 'Sign in');

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

Il y a une entrée pour le nom d'utilisateur et le mot de passe.

Modèle

Le formulaire sera rendu dans le modèle in.latte

{block content}
<h1 n:block=title>Sign in</h1>

{control signInForm}

Gestionnaire de connexion

Nous ajoutons également un manipulateur de formulaire pour la connexion de l'utilisateur, qui est invoqué juste après la soumission du formulaire.

Le gestionnaire prendra simplement le nom d'utilisateur et le mot de passe que l'utilisateur a entré et les passera à l'authentificateur défini plus tôt. Après que l'utilisateur se soit connecté, nous le redirigerons vers la page d'accueil.

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('Incorrect username or password.');
	}
}

La méthode User::login() doit lever une exception lorsque le nom d'utilisateur ou le mot de passe ne correspond pas à ceux que nous avons définis précédemment. Comme nous le savons déjà, cela entraînerait un écran rouge de Tracy ou, en mode production, un message informant d'une erreur interne du serveur. Nous n'aimerions pas cela. C'est pourquoi nous attrapons l'exception et ajoutons un message d'erreur sympathique au formulaire.

Lorsqu'une erreur se produit dans le formulaire, la page contenant le formulaire est à nouveau affichée et, au-dessus du formulaire, un message sympathique informe l'utilisateur qu'il a saisi un nom d'utilisateur ou un mot de passe incorrect.

Sécurité des présentateurs

Nous allons sécuriser un formulaire pour l'ajout et la modification des messages. Il est défini dans le présentateur EditPresenter. L'objectif est d'empêcher les utilisateurs qui ne sont pas connectés d'accéder à la page.

Nous créons une méthode startup() qui est lancée immédiatement au début du cycle de vie du présentateur. Cette méthode redirige les utilisateurs non connectés vers le formulaire de connexion.

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

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

Un utilisateur non authentifié ne peut plus voir les pages créer et modifier, mais il peut toujours voir les liens qui y mènent. Cachons-les également. L'un de ces liens se trouve sur app/UI/Home/default.latte, et il ne doit être visible que si l'utilisateur est connecté.

Nous pouvons le masquer en utilisant n:attribut appelé n:if. Si la déclaration qu'il contient est false, l'ensemble de la balise <a> et son contenu ne seront pas affichés :

<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a>

ceci est un raccourci pour (ne pas confondre avec tag-if) :

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

Vous devez masquer le lien d'édition situé dans app/UI/Post/show.latte de manière similaire.

Hé, mais comment accéder à la page de connexion ? Il n'y a pas de lien qui pointe vers elle. Ajoutons-en un dans le fichier modèle @layout.latte. Essayez de trouver un endroit sympa, ça peut être n'importe quel endroit qui vous plaît le plus.

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

Si l'utilisateur n'est pas encore connecté, nous afficherons le lien “Sign in”. Sinon, nous affichons le lien “Sign out”. Nous ajoutons cette action dans SignPresenter.

L'action de déconnexion ressemble à ceci, et parce que nous redirigeons l'utilisateur immédiatement, il n'y a pas besoin d'un modèle de vue.

public function actionOut(): void
{
	$this->getUser()->logout();
	$this->flashMessage('You have been signed out.');
	$this->redirect('Home:');
}

Elle appelle simplement la méthode logout() et affiche ensuite un message sympathique à l'utilisateur.

Résumé

Nous avons un lien pour se connecter et aussi pour déconnecter l'utilisateur. Nous avons utilisé l'authentificateur intégré pour l'authentification et les détails de connexion sont dans le fichier de configuration car il s'agit d'une simple application de test. Nous avons également sécurisé les formulaires d'édition afin que seuls les utilisateurs connectés puissent ajouter et modifier des articles.

Ici vous pouvez lire plus sur la connexion et l'autorisation des utilisateurs.