Uwierzytelnianie

Nette zapewnia sposób na zaprogramowanie uwierzytelniania na naszej stronie, ale nie zmusza nas do niczego. Realizacja zależy od nas samych. Nette zawiera interfejs Nette\Security\Authenticator, który wymaga tylko jednej metody authenticate, która uwierzytelnia użytkownika jakkolwiek chcemy.

Istnieje wiele sposobów uwierzytelnienia użytkownika. Najczęstszą metodą uwierzytelniania jest użycie hasła (użytkownik podaje swoje imię i nazwisko lub e-mail i hasło), ale istnieją też inne sposoby. Być może znasz przyciski typu “Zaloguj się za pomocą Facebooka”, lub zaloguj się za pomocą Google/Twitter/GitHub na niektórych stronach. W przypadku Nette możemy mieć dowolną metodę logowania lub możemy je łatwo połączyć. To zależy od nas.

Normalnie napisalibyśmy własny authenticator, ale dla tego prostego małego bloga użyjemy wbudowanego authenticatora, który loguje się na podstawie hasła i nazwy użytkownika zapisanych w pliku konfiguracyjnym. Jest to dobre rozwiązanie do celów testowych. Dodajemy więc do pliku konfiguracyjnego następującą sekcję security config/common.neon:

security:
	users:
		admin: secret # użytkownik 'admin', hasło 'secret'

Nette automatycznie utworzy usługę w kontenerze DI.

Formularz logowania

Teraz mamy już gotowe uwierzytelnienie i musimy przygotować interfejs użytkownika do logowania. Stwórzmy więc nowy prezenter o nazwie SignPresenter, który:

  • wyświetla formularz logowania (z nazwą i hasłem)
  • uwierzytelnia użytkownika po wysłaniu formularza
  • zapewnia opcję wylogowania

Zacznijmy od formularza logowania. Wiemy już jak działają formularze w presenterech. Stwórzmy więc prezenter SignPresenter i napiszmy metodę createComponentSignInForm. Powinna ona wyglądać coś takiego:

<?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', 'Uživatelské jméno:')
			->setRequired('Prosím vyplňte své uživatelské jméno.');

		$form->addPassword('password', 'Heslo:')
			->setRequired('Prosím vyplňte své heslo.');

		$form->addSubmit('send', 'Přihlásit');

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

Znajdują się tam pola na nazwę użytkownika i hasło.

Szablon

Formularz zostanie wyrenderowany w szablonie in.latte:

{block content}
<h1 n:block=title>Přihlášení</h1>

{control signInForm}

Wywołanie zwrotne logowania

Następnie dodamy callback dla logowania użytkownika, który zostanie wywołany zaraz po pomyślnym przesłaniu formularza.

Callback po prostu bierze nazwę użytkownika i hasło, które użytkownik wypełnił i przekazuje je do authenticatora. Po zalogowaniu przekierowujemy na stronę główną.

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('Nesprávné přihlašovací jméno nebo heslo.');
	}
}

Metoda User::login() rzuca wyjątek, jeśli nazwa użytkownika i hasło nie pasują do informacji zawartych w pliku konfiguracyjnym. Jak już wiemy, może to skutkować czerwoną stroną błędu lub, w trybie produkcyjnym, komunikatem informującym o errorem serwera. Jednak my tego nie chcemy. Dlatego łapiemy ten wyjątek i przekazujemy do formularza ładny, przyjazny dla użytkownika komunikat o błędzie.

Gdy w formularzu pojawi się błąd, strona formularza zostanie przerysowana, a nad formularzem pojawi się ładny komunikat informujący użytkownika, że wypełnił zły login lub hasło.

Bezpieczeństwo prezentera

Udostępniamy formularz do dodawania i edycji prezenterów. Jest to zdefiniowane w prezenterze EditPresenter. Celem jest uniemożliwienie dostępu do strony użytkownikom, którzy nie są zalogowani.

Stworzymy metodę startup(), która jest uruchamiana natychmiast na początku cyklu życia prezentera. To przekierowuje niezalogowanych użytkowników do formularza logowania.

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

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

Nieautoryzowany użytkownik nie może już zobaczyć strony create lub edit, ale nadal może zobaczyć linki do nich. Te również powinniśmy ukryć. Jeden z takich linków znajduje się w szablonie app/UI/Home/default.latte i powinien być widoczny tylko dla zalogowanych użytkowników.

Możemy go ukryć używając n:atrybutu w imieniu n:if. Jeśli ten warunek jest false, to cały znacznik <a>, łącznie z treścią, pozostanie ukryty.

<a n:href="Edit:create" n:if="$user->isLoggedIn()">Vytvořit příspěvek</a>

co jest skrótem następującej notacji (nie mylić z tag-if):

{if $user->isLoggedIn()}<a n:href="Edit:create">Vytvořit příspěvek</a>{/if}

W ten sam sposób ukrywamy link w szablonie app/UI/Post/show.latte.

Jak właściwie dostać się na stronę logowania? Nie ma żadnego linku, który by do niego prowadził. Dodajmy więc go do szablonu @layout.latte. Spróbujcie znaleźć odpowiednie miejsce – może to być niemal każde.

...
<ul class="navig">
	<li><a n:href="Home:">Články</a></li>
	{if $user->isLoggedIn()}
		<li><a n:href="Sign:out">Odhlásit</a></li>
	{else}
		<li><a n:href="Sign:in">Přihlásit</a></li>
	{/if}
</ul>
...

Jeśli użytkownik nie jest zalogowany, pojawi się link “Zaloguj się”. W przeciwnym razie pojawi się link “Wyloguj”. Dodamy też tę akcję do strony SignPresenter.

Ponieważ przekierowujemy użytkownika od razu po wylogowaniu, nie jest potrzebny żaden szablon. Wylogowanie wygląda tak:

public function actionOut(): void
{
	$this->getUser()->logout();
	$this->flashMessage('Odhlášení bylo úspěšné.');
	$this->redirect('Home:');
}

Wystarczy wywołać metodę logout(), a następnie wyświetlić ładny komunikat potwierdzający pomyślne wylogowanie.

Streszczenie.

Mamy link do logowania, a także wylogowania użytkowników. Użyliśmy wbudowanego authenticatora do uwierzytelniania i mamy dane logowania w pliku konfiguracyjnym, ponieważ jest to prosta aplikacja testowa. Zabezpieczyliśmy również formularze edycji, aby tylko zalogowani użytkownicy mogli dodawać i edytować posty.

Więcej o weryfikacji logowaniauprawnień użytkowników można przeczytać tutaj.