Authentifizierung

Nette bietet eine Möglichkeit, die Authentifizierung auf unseren Seiten zu programmieren, zwingt uns aber zu nichts. Die Implementierung liegt ganz bei uns. Nette enthält das Interface Nette\Security\Authenticator, das nur eine Methode authenticate erfordert, die den Benutzer auf beliebige Weise überprüft.

Es gibt viele Möglichkeiten, wie ein Benutzer authentifiziert werden kann. Die häufigste Methode ist die Authentifizierung per Passwort (der Benutzer gibt seinen Namen oder seine E-Mail-Adresse und sein Passwort an), aber es gibt auch andere Methoden. Vielleicht kennen Sie die Schaltflächen „Mit Facebook anmelden“ oder die Anmeldung über Google/Twitter/GitHub auf einigen Websites. Mit Nette können wir jede beliebige Anmeldemethode verwenden oder sie sogar kombinieren. Es liegt ganz bei uns.

Normalerweise würden wir unseren eigenen Authenticator schreiben, aber für diesen einfachen kleinen Blog verwenden wir den integrierten Authenticator, der die Anmeldung anhand eines in der Konfigurationsdatei gespeicherten Passworts und Benutzernamens durchführt. Er eignet sich gut für Testzwecke. Fügen wir also den folgenden security-Abschnitt zur Konfigurationsdatei config/common.neon hinzu:

security:
	users:
		admin: secret  # Benutzer 'admin', Passwort 'secret'

Nette erstellt automatisch einen Dienst im DI-Container.

Anmeldeformular

Nun haben wir die Authentifizierung vorbereitet und müssen die Benutzeroberfläche für die Anmeldung erstellen. Erstellen wir also einen neuen Presenter namens SignPresenter, der:

  • das Anmeldeformular anzeigt (mit Anmeldenamen und Passwort)
  • nach dem Absenden des Formulars den Benutzer authentifiziert
  • die Möglichkeit zur Abmeldung bietet

Beginnen wir mit dem Anmeldeformular. Wir wissen bereits, wie Formulare in Presentern funktionieren. Erstellen wir also den Presenter SignPresenter und schreiben die Methode createComponentSignInForm. Sie sollte etwa so aussehen:

<?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', 'Benutzername:')
			->setRequired('Bitte geben Sie Ihren Benutzernamen ein.');

		$form->addPassword('password', 'Passwort:')
			->setRequired('Bitte geben Sie Ihr Passwort ein.');

		$form->addSubmit('send', 'Anmelden');

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

Es gibt Felder für Benutzername und Passwort.

Template

Das Formular wird im Template in.latte gerendert:

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

{control signInForm}

Anmelde-Callback

Als Nächstes fügen wir den Callback für die Benutzeranmeldung hinzu, der direkt nach dem erfolgreichen Absenden des Formulars aufgerufen wird.

Der Callback übernimmt lediglich den vom Benutzer eingegebenen Benutzernamen und das Passwort und übergibt sie an den Authenticator. Nach der Anmeldung leiten wir zur Startseite weiter.

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('Falscher Benutzername oder falsches Passwort.');
	}
}

Die Methode User::login() löst eine Ausnahme aus, wenn Benutzername und Passwort nicht mit den Angaben in der Konfigurationsdatei übereinstimmen. Wie wir bereits wissen, kann dies zu einer roten Fehlerseite oder im Produktionsmodus zu einer Meldung über einen Serverfehler führen. Das wollen wir jedoch nicht. Daher fangen wir diese Ausnahme ab und übergeben eine schöne, benutzerfreundliche Fehlermeldung an das Formular.

Sobald ein Fehler im Formular auftritt, wird die Seite mit dem Formular neu gezeichnet und über dem Formular wird eine nette Nachricht angezeigt, die den Benutzer darüber informiert, dass er einen falschen Benutzernamen oder ein falsches Passwort eingegeben hat.

Absicherung von Presentern

Wir sichern das Formular zum Hinzufügen und Bearbeiten von Beiträgen ab. Dieses ist im Presenter EditPresenter definiert. Ziel ist es, Benutzern, die nicht angemeldet sind, den Zugriff auf die Seite zu verwehren.

Wir erstellen die Methode startup(), die sofort zu Beginn des Lebenszyklus des Presenters ausgeführt wird. Sie leitet nicht angemeldete Benutzer zum Anmeldeformular weiter.

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

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

Ein nicht autorisierter Benutzer kann die Seiten create und edit nicht mehr sehen, aber er kann immer noch die Links dorthin sehen. Diese sollten wir ebenfalls verbergen. Ein solcher Link befindet sich im Template app/Presentation/Home/default.latte und sollte nur für angemeldete Benutzer sichtbar sein.

Wir können ihn mithilfe des n:Attributs namens n:if verbergen. Wenn diese Bedingung false ist, bleibt das gesamte <a>-Tag einschließlich seines Inhalts verborgen.

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

was eine Abkürzung für die folgende Schreibweise ist (nicht zu verwechseln mit tag-if):

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

Auf die gleiche Weise verbergen wir auch den Link im Template app/Presentation/Post/show.latte.

Wie gelangen wir eigentlich zur Anmeldeseite? Es gibt keinen Link, der dorthin führt. Fügen wir ihn also dem Template @layout.latte hinzu. Versuchen Sie, einen geeigneten Platz zu finden – er kann fast überall sein.

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

Wenn der Benutzer nicht angemeldet ist, wird der Link “Anmelden” angezeigt. Andernfalls wird der Link “Abmelden” angezeigt. Diese Aktion fügen wir auch dem SignPresenter hinzu.

Da wir den Benutzer nach der Abmeldung sofort weiterleiten, ist kein Template erforderlich. Die Abmeldung sieht wie folgt aus:

public function actionOut(): void
{
	$this->getUser()->logout();
	$this->flashMessage('Abmeldung erfolgreich.');
	$this->redirect('Home:');
}

Es wird nur die Methode logout() aufgerufen und anschließend eine nette Nachricht angezeigt, die die erfolgreiche Abmeldung bestätigt.

Zusammenfassung

Wir haben einen Link zur Anmeldung und auch zur Abmeldung des Benutzers. Zur Authentifizierung haben wir den integrierten Authenticator verwendet und die Anmeldedaten befinden sich in der Konfigurationsdatei, da es sich um eine einfache Testanwendung handelt. Wir haben auch die Bearbeitungsformulare gesichert, sodass nur angemeldete Benutzer Beiträge hinzufügen und bearbeiten können.

Hier können Sie mehr über die Benutzeranmeldung und die Berechtigungsprüfung lesen.