Authentifizierung

Nette bietet einen Weg, auf unseren Seiten eine Authentifizierung umzusetzen, zwingt uns aber keinen bestimmten Ansatz auf. Die Umsetzung liegt ganz bei uns. Nette enthält das Interface Nette\Security\Authenticator, das nur eine einzige Methode verlangt, authenticate, um den Benutzer auf beliebige Weise zu prüfen.

Es gibt viele Wege, einen Benutzer zu authentifizieren. Am häufigsten ist die Authentifizierung per Passwort (bei der der Benutzer seinen Namen oder seine E-Mail-Adresse und ein Passwort angibt), aber es gibt auch andere Verfahren. Sie kennen vielleicht Schaltflächen wie “Mit Facebook anmelden” oder die Anmeldung über Google/Twitter/GitHub auf manchen Seiten. Mit Nette können wir jede beliebige Anmeldemethode haben oder sie sogar kombinieren. Das ist ganz unsere Entscheidung.

Üblicherweise würden wir einen eigenen Authenticator schreiben. Für diesen einfachen Blog verwenden wir jedoch den eingebauten Authenticator, der anhand eines Benutzernamens und eines Passworts aus der Konfigurationsdatei anmeldet. Das eignet sich zu Testzwecken. Ergänzen wir in der Konfigurationsdatei config/common.neon den folgenden Abschnitt security:

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

Nette erzeugt für diesen Authenticator automatisch einen Service im DI-Container.

Anmeldeformular

Nun, da die Authentifizierung eingerichtet ist, müssen wir eine Benutzeroberfläche für die Anmeldung erstellen. Erstellen wir einen neuen Presenter namens SignPresenter, der:

  • ein Anmeldeformular anzeigt (mit Feldern für Benutzernamen und Passwort)
  • den Benutzer beim Absenden des Formulars authentifiziert
  • eine Möglichkeit zum Abmelden bietet

Wir beginnen mit dem Anmeldeformular. Wie Formulare in Presentern funktionieren, wissen wir bereits. Erstellen wir den SignPresenter und schreiben die Methode createComponentSignInForm. Sie sollte ungefähr 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 den Benutzernamen und das Passwort.

Template

Das Formular wird im Template in.latte gerendert:

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

{control signInForm}

Anmelde-Callback

Als Nächstes ergänzen wir die Callback-Methode zum Anmelden des Benutzers, die unmittelbar nach dem erfolgreichen Absenden des Formulars aufgerufen wird.

Der Callback nimmt einfach den vom Benutzer eingegebenen Benutzernamen und das Passwort entgegen und übergibt sie an den Authenticator. Nach erfolgreicher Anmeldung leiten wir auf die 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() wirft eine Exception, wenn Benutzername und Passwort nicht zu den Zugangsdaten in der Konfigurationsdatei passen. Wie wir wissen, führt das zum roten Bildschirm von Tracy oder im Produktionsmodus zu einer Fehlermeldung des Servers. Das wollen wir nicht. Deshalb fangen wir die Exception ab und ergänzen im Formular eine schöne, benutzerfreundliche Fehlermeldung.

Tritt im Formular ein Fehler auf, wird die Seite mit dem Formular erneut gerendert, und über dem Formular erscheint eine Meldung, die den Benutzer darauf hinweist, dass er einen falschen Benutzernamen oder ein falsches Passwort eingegeben hat.

Presenter absichern

Wir sichern das Formular zum Hinzufügen und Bearbeiten von Beiträgen ab, das im EditPresenter definiert ist. Ziel ist es, zu verhindern, dass nicht angemeldete Benutzer auf die Seite zugreifen.

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

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

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

Ein nicht berechtigter Benutzer sieht die Seiten create und edit nicht mehr, kann aber weiterhin die Links sehen, die auf sie zeigen. Auch die sollten wir verbergen. Einer dieser Links steht im Template app/Presentation/Home/default.latte und sollte nur für angemeldete Benutzer sichtbar sein.

Verbergen können wir ihn mit einem n:Attribut namens n:if. Ist die Bedingung false, wird der gesamte Tag <a> samt seinem Inhalt nicht angezeigt.

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

was eine Kurzform der folgenden Schreibweise ist (verwechseln Sie es nicht mit tag-if):

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

Verbergen Sie den Link zum Bearbeiten im Template app/Presentation/Post/show.latte auf dieselbe Weise.

Wie kommen wir eigentlich zur Anmeldeseite? Es gibt keinen Link, der dorthin führt. Ergänzen wir einen im Template @layout.latte. Versuchen Sie, eine passende Stelle zu finden – es kann fast überall sein.

...
<ul class="navig">
	<li><a n:href="Home:">Startseite</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>
...

Ist der Benutzer nicht angemeldet, wird der Link “Anmelden” angezeigt. Andernfalls erscheint der Link “Abmelden”. Diese Aktion ergänzen wir außerdem im SignPresenter.

Da wir den Benutzer sofort nach dem Abmelden weiterleiten, ist kein Template nötig. Die Aktion zum Abmelden sieht so aus:

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

Sie ruft einfach die Methode logout() auf und zeigt dem Benutzer dann eine Bestätigung an.

Zusammenfassung

Wir haben Links zum An- und Abmelden des Benutzers. Zur Authentifizierung haben wir den eingebauten Authenticator verwendet, und die Zugangsdaten stehen in der Konfigurationsdatei, denn es handelt sich um eine einfache Testanwendung. Außerdem haben wir die Bearbeitungsformulare abgesichert, sodass nur angemeldete Benutzer Beiträge hinzufügen und bearbeiten können.

Mehr über die Anmeldung von Benutzern und die Autorisierung können Sie hier lesen.