Uwierzytelnianie
Nette daje sposób na zrealizowanie uwierzytelniania na naszych stronach, ale nie narzuca żadnego konkretnego podejścia.
Implementacja zależy wyłącznie od nas. Nette zawiera interfejs Nette\Security\Authenticator, który wymaga tylko
jednej metody, authenticate, żeby zweryfikować użytkownika w dowolny wybrany przez nas sposób.
Sposobów uwierzytelnienia użytkownika jest wiele. Najczęstszą metodą jest uwierzytelnianie hasłem (gdzie użytkownik podaje swoją nazwę albo e-mail i hasło), ale istnieją też inne. Możesz znać przyciski “Zaloguj przez Facebooka” albo logowanie przez Google/Twittera/GitHuba na niektórych stronach. Z Nette możemy mieć dowolną metodę logowania, a nawet je łączyć. To całkowicie nasza decyzja.
Zwykle napisalibyśmy własny authenticator. Dla tego prostego bloga użyjemy jednak wbudowanego authenticatora, który loguje
na podstawie nazwy użytkownika i hasła przechowywanych w pliku konfiguracyjnym. Nadaje się to do celów testowych. Dodajmy do
pliku konfiguracyjnego config/common.neon poniższą sekcję security:
security:
users:
admin: secret # użytkownik 'admin', hasło 'secret'
Nette automatycznie tworzy w kontenerze DI usługę dla tego authenticatora.
Formularz logowania
Skoro uwierzytelnianie jest już ustawione, musimy utworzyć interfejs użytkownika do logowania. Utwórzmy nowy presenter
o nazwie SignPresenter, który będzie:
- wyświetlać formularz logowania (z polami nazwy użytkownika i hasła)
- uwierzytelniać użytkownika po wysłaniu formularza
- dawać możliwość wylogowania
Zaczniemy od formularza logowania. Wiemy już, jak formularze działają w presenterach. Utwórzmy SignPresenter
i napiszmy metodę createComponentSignInForm. Powinna wyglądać mniej więcej tak:
<?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', 'Nazwa użytkownika:')
->setRequired('Podaj swoją nazwę użytkownika.');
$form->addPassword('password', 'Hasło:')
->setRequired('Podaj swoje hasło.');
$form->addSubmit('send', 'Zaloguj się');
$form->onSuccess[] = $this->signInFormSucceeded(...);
return $form;
}
}
Są tam pola na nazwę użytkownika i hasło.
Szablon
Formularz zostanie wyrenderowany w szablonie in.latte:
{block content}
<h1 n:block=title>Logowanie</h1>
{control signInForm}
Callback logowania
Następnie dodamy metodę callback logującą użytkownika, która zostanie wywołana zaraz po udanym wysłaniu formularza.
Callback po prostu bierze nazwę użytkownika i hasło wpisane przez użytkownika i przekazuje je authenticatorowi. Po udanym 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('Nieprawidłowa nazwa użytkownika albo hasło.');
}
}
Metoda User::login() rzuca wyjątek, jeśli nazwa użytkownika i hasło nie odpowiadają danym z pliku konfiguracyjnego. Jak wiemy, mogłoby to skończyć się czerwonym ekranem Tracy albo, w trybie produkcyjnym, komunikatem o błędzie serwera. Tego nie chcemy. Dlatego przechwytujemy wyjątek i dodajemy do formularza ładny, przyjazny dla użytkownika komunikat o błędzie.
Gdy w formularzu wystąpi błąd, strona z formularzem renderuje się ponownie, a nad formularzem pojawia się komunikat informujący użytkownika, że wpisał złą nazwę użytkownika albo hasło.
Zabezpieczanie presenterów
Zabezpieczymy formularz do dodawania i edycji wpisów zdefiniowany w EditPresenter. Celem jest uniemożliwienie
dostępu do strony użytkownikom, którzy nie są zalogowani.
Utworzymy metodę startup(), która uruchamia się zaraz na początku cyklu życia presentera. Metoda ta
przekieruje niezalogowanych użytkowników do formularza logowania.
public function startup(): void
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
Ukrywanie odnośników
Nieuprawniony użytkownik nie zobaczy już stron create ani edit, ale nadal widzi wskazujące na nie odnośniki.
Powinniśmy je również ukryć. Jeden z takich odnośników jest w szablonie app/Presentation/Home/default.latte
i powinien być widoczny tylko dla zalogowanych użytkowników.
Możemy go ukryć za pomocą n:atrybutu o nazwie n:if. Jeśli warunek jest false, cały tag
<a> wraz z zawartością nie zostanie wyświetlony.
<a n:href="Edit:create" n:if="$user->isLoggedIn()">Utwórz wpis</a>
co jest skrótem poniższego zapisu (nie myl tego z tag-if):
{if $user->isLoggedIn()}<a n:href="Edit:create">Utwórz wpis</a>{/if}
W ten sam sposób ukryj odnośnik do edycji w szablonie app/Presentation/Post/show.latte.
Odnośnik do logowania
Jak właściwie dostać się na stronę logowania? Nie prowadzi do niej żaden odnośnik. Dodajmy go do szablonu
@layout.latte. Spróbuj znaleźć odpowiednie miejsce, może być praktycznie gdziekolwiek.
...
<ul class="navig">
<li><a n:href="Home:">Strona główna</a></li>
{if $user->isLoggedIn()}
<li><a n:href="Sign:out">Wyloguj się</a></li>
{else}
<li><a n:href="Sign:in">Zaloguj się</a></li>
{/if}
</ul>
...
Jeśli użytkownik nie jest zalogowany, wyświetla się odnośnik “Zaloguj się”. W przeciwnym razie pokazuje się
odnośnik “Wyloguj się”. Dodamy jeszcze tę akcję do SignPresenter.
Ponieważ użytkownika przekierowujemy zaraz po wylogowaniu, szablon nie jest potrzebny. Akcja wylogowania wygląda tak:
public function actionOut(): void
{
$this->getUser()->logout();
$this->flashMessage('Zostałeś wylogowany.');
$this->redirect('Home:');
}
Po prostu wywołuje metodę logout(), a potem wyświetla użytkownikowi komunikat potwierdzający.
Podsumowanie
Mamy odnośniki do zalogowania i wylogowania użytkownika. Do uwierzytelniania użyliśmy wbudowanego authenticatora, a dane logowania są w pliku konfiguracyjnym, bo to prosta aplikacja testowa. Zabezpieczyliśmy też formularze edycyjne, żeby dodawać i edytować wpisy mogli tylko zalogowani użytkownicy.
Więcej o logowaniu użytkowników i autoryzacji przeczytasz tutaj.