Uwierzytelnianie
Nette dostarcza sposób na zaprogramowanie uwierzytelniania na naszych stronach, ale do niczego nas nie zmusza. Implementacja
zależy wyłącznie od nas. Nette zawiera interfejs Nette\Security\Authenticator
, który wymaga tylko jednej metody
authenticate
, która weryfikuje użytkownika w dowolny sposób, jaki zechcemy.
Istnieje wiele możliwości, jak użytkownik może zostać uwierzytelniony. Najczęstszym sposobem uwierzytelniania jest użycie hasła (użytkownik podaje swoją nazwę lub e-mail i hasło), ale są też inne sposoby. Być może znasz przyciski typu “Zaloguj przez Facebooka” lub logowanie za pomocą Google/Twitter/GitHub na niektórych stronach. Z Nette możemy mieć dowolną metodę logowania, lub możemy je nawet łączyć. To zależy tylko od nas.
Zwykle napisalibyśmy własny authenticator, ale dla tego prostego małego bloga użyjemy wbudowanego authenticatora, który
loguje na podstawie hasła i nazwy użytkownika zapisanych w pliku konfiguracyjnym. Przydaje się do celów testowych. Dodajmy
więc następującą sekcję security
do pliku konfiguracyjnego 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 przygotowane uwierzytelnianie i musimy przygotować interfejs użytkownika do logowania. Utwórzmy więc nowy
presenter o nazwie SignPresenter
, który:
- wyświetli formularz logowania (z nazwą użytkownika i hasłem)
- po wysłaniu formularza zweryfikuje użytkownika
- zapewni możliwość wylogowania
Zaczniemy od formularza logowania. Już wiemy, jak działają formularze w prezenterach. Utworzymy więc presenter
SignPresenter
i napiszemy metodę createComponentSignInForm
. Powinien 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('Proszę podać swoją nazwę użytkownika.');
$form->addPassword('password', 'Hasło:')
->setRequired('Proszę podać swoje hasło.');
$form->addSubmit('send', 'Zaloguj');
$form->onSuccess[] = $this->signInFormSucceeded(...);
return $form;
}
}
Są tu pola na nazwę użytkownika i hasło.
Szablon
Formularz będzie renderowany w szablonie in.latte
:
{block content}
<h1 n:block=title>Logowanie</h1>
{control signInForm}
Callback logowania
Następnie dodamy callback do logowania użytkownika, który będzie wywoływany zaraz po pomyślnym wysłaniu formularza.
Callback jedynie pobierze nazwę użytkownika i hasło, które użytkownik wypełnił, i przekaże je do authenticatora. Po zalogowaniu przekierujemy 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 lub hasło.');
}
}
Metoda User::login() rzuci wyjątek, jeśli nazwa użytkownika i hasło nie zgadzają się z danymi w pliku konfiguracyjnym. Jak już wiemy, może to skutkować czerwoną stroną błędu lub, w trybie produkcyjnym, komunikatem informującym o błędzie serwera. Tego jednak nie chcemy. Dlatego przechwycimy ten wyjątek i przekażemy ładny, przyjazny dla użytkownika komunikat błędu do formularza.
Gdy w formularzu wystąpi błąd, strona z formularzem zostanie ponownie wyrenderowana, a nad formularzem pojawi się ładny komunikat informujący użytkownika, że podał złą nazwę użytkownika lub hasło.
Zabezpieczenie prezenterów
Zabezpieczymy formularz do dodawania i edytowania postów. Jest on zdefiniowany w presenterze EditPresenter
.
Celem jest uniemożliwienie dostępu do strony użytkownikom, którzy nie są zalogowani.
Utworzymy metodę startup()
, która uruchamia się natychmiast na początku cyklu życia presentera. Przekieruje ona
niezalogowanych użytkowników do formularza logowania.
public function startup(): void
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
Ukrywanie linków
Nieautoryzowany użytkownik już nie może zobaczyć strony create
ani edit
, ale nadal może widzieć
do nich linki. Te również powinniśmy ukryć. Jeden taki link znajduje się w szablonie
app/Presentation/Home/default.latte
i powinni go widzieć tylko zalogowani użytkownicy.
Możemy go ukryć za pomocą n:atrybutu o nazwie n:if
. Jeśli ten warunek jest false
, cały
tag <a>
, wraz z zawartością, pozostanie ukryty.
<a n:href="Edit:create" n:if="$user->isLoggedIn()">Utwórz post</a>
co jest skrótem następującego zapisu (nie mylić z tag-if
):
{if $user->isLoggedIn()}<a n:href="Edit:create">Utwórz post</a>{/if}
W ten sam sposób ukryjemy również link w szablonie app/Presentation/Post/show.latte
.
Link do logowania
Jak właściwie dostaniemy się na stronę logowania? Nie ma tu żadnego linku, który by do niej prowadził. Dodajmy go więc
do szablonu @layout.latte
. Spróbuj znaleźć odpowiednie miejsce – może to być prawie wszędzie.
...
<ul class="navig">
<li><a n:href="Home:">Artykuły</a></li>
{if $user->isLoggedIn()}
<li><a n:href="Sign:out">Wyloguj</a></li>
{else}
<li><a n:href="Sign:in">Zaloguj</a></li>
{/if}
</ul>
...
Jeśli użytkownik nie jest zalogowany, wyświetli się link “Zaloguj”. W przeciwnym razie wyświetli się link
“Wyloguj”. Tę akcję również dodamy do SignPresenter
.
Ponieważ użytkownika po wylogowaniu natychmiast przekierowujemy, nie jest potrzebny żaden szablon. Wylogowanie wygląda tak:
public function actionOut(): void
{
$this->getUser()->logout();
$this->flashMessage('Wylogowanie powiodło się.');
$this->redirect('Home:');
}
Wywołuje się tylko metodę logout()
, a następnie wyświetla się ładny komunikat potwierdzający pomyślne
wylogowanie.
Podsumowanie
Mamy link do logowania i wylogowania użytkownika. Do uwierzytelniania użyliśmy wbudowanego authenticatora, a dane logowania mamy w pliku konfiguracyjnym, ponieważ jest to prosta aplikacja testowa. Zabezpieczyliśmy również formularze edycji, więc dodawać i edytować posty mogą tylko zalogowani użytkownicy.
Tutaj możesz przeczytać więcej o logowaniu użytkowników i autoryzacji.