Autentifikace | První aplikace

Nette poskytuje způsob jak naprogramovat autentifikaci na našich stránkách, ale do ničeho nás nenutí. Implementace je pouze na nás. Nette obsahuje rozhraní Nette\Security\Authenticator, které vyžaduje pouze jednu metodu authenticate, která ověřuje uživatele jakkoliv budeme chtít.

Existuje mnoho možností, jak může být uživatel ověřen. Nejčastější způsob ověření je pomocí hesla (uživatel poskytne své jméno, nebo e-mail a heslo), ale jsou zde i jiné způsoby. Možná znáte tlačítka typu „Přihlásit pomocí Facebooku“, nebo přihlášení pomocí Google/Twitter/GitHub na některých stránkách. S Nette můžeme mít jakoukoliv přihlašovací metodu, nebo je klidně můžeme kombinovat. Je to jen na nás.

Obyčejně bychom si napsali vlastní authenticator, ale pro tento jednoduchý malý blog použijeme vestavěný authenticator, který přihlašuje na základě hesla a uživatelského jména uloženého v konfiguračním souboru. Hodí se pro testovací účely. Přidáme tedy následující security sekci do konfiguračního souboru config/common.neon:

security:
	users:
		admin: secret  # user 'admin', password 'secret'

Nette automaticky vytvoří službu v DI kontejneru.

Více si můžete přečíst o dependency injection zde a o konfiguraci zde.

Přihlašovací formulář

Nyní máme autentifikaci připravenu a musíme připravit uživatelské rozhraní pro přihlášení. Vytvořme tedy nový presenter s názvem SignPresenter, který:

  • zobrazí přihlašovací formulář (s přihlašovacím jménem a heslem)
  • po odeslání formuláře ověří uživatele
  • poskytne možnost odhlášení

Začneme s přihlašovacím formulářem. Již víme, jak formuláře v presenterech fungují. Vytvoříme si tedy presenter SignPresenter a zapíšeme metodu createComponentSignInForm. Měl by vypadat nějak takto:

namespace App\Presenters;

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;
	}
}

Jsou zde políčka pro uživatelské jméno a heslo.

Šablona

Formulář se bude vykreslovat v šabloně app/Presenters/templates/Sign/in.latte:

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

{control signInForm}

Přihlašovací callback

Dále doplníme callback pro přihlášení uživatele, který bude volán hned po úspěšném odeslání formuláře.

Callback pouze převezme uživatelské jméno a heslo, které uživatel vyplnil a předá je authenticatoru. Po přihlášení přesměrujeme na úvodní stránku.

public function signInFormSucceeded(Form $form, \stdClass $values): void
{
	try {
		$this->getUser()->login($values->username, $values->password);
		$this->redirect('Homepage:');

	} catch (Nette\Security\AuthenticationException $e) {
		$form->addError('Nesprávné přihlašovací jméno nebo heslo.');
	}
}

Metoda User::login() vyhodí výjimku, pokud uživatelské jméno a heslo nesouhlasí s údaji v konfiguračním souboru. Jak již víme, toto může vyústit v červenou chybovou stránku, nebo v produkčním módu ve zprávu informující o server erroru. To však nechceme. Proto tuto výjimku zachytíme a předáme hezkou, uživatelsky přívětivou chybovou zprávu do formuláře.

Jakmile dojde k chybě ve formuláři, stránka s formulářem se překreslí a nad formulářem se zobrazí hezká zpráva informující uživatele, že vyplnil špatné přihlašovací jméno nebo heslo.

Zabezpečení presenterů

Zabezpečíme formulář pro přidávání a editování příspěvků. Ten je definován v presenteru EditPresenter. Cílem je znemožnit přístup na stránku uživatelům, kteří nejsou přihlášeni.

Vytvoříme metodu startup(), která se spouští ihned na začátku životního cyklu presenteru. Ta přesměruje nepřihlášené uživatele na formulář s přihlášením.

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

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

Skrytí odkazů

Neautorizovaný uživatel už nemůže vidět stránku create ani edit, ale stále na ně může vidět odkazy. Ty bychom měli také schovat. Jeden takový odkaz je v šabloně app/Presenters/templates/Homepage/default.latte a měli by jej vidět pouze přihlášení uživatelé.

Můžeme jej schovat využitím n:atributu jménem n:if. Pokud je tato podmínka false, celý tag <a>, včetně obsahu, zůstane skrytý.

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

což je zkratka následujícího zápisu (neplést s tag-if):

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

Stejným způsobem skryjeme také odkaz v šabloně app/Presenters/templates/Post/show.latte.

Odkaz na přihlášení

Jak se vlastně dostaneme na přihlašovací stránku? Není zde žádný odkaz, který by na ni vedl. Tak si ho tedy přidáme do šablony app/Presenters/templates/@layout.latte. Pokuste se najít vhodné místo – může to být téměř kdekoliv.

<ul class="navig">
	<li><a n:href="Homepage:">Č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>

Pokud není uživatel přihlášen, zobrazí se odkaz „Přihlásit“. V opačném případě se zobrazí odkaz „Odhlásit“. Tuto akci také doplníme do SignPresenter.

Jelikož uživatele po odhlášení okamžitě přesměrujeme, není potřeba žádná šablona. Odhlášení vypadá takto:

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

Pouze se zavolá metoda logout() a následně se zobrazí hezká zpráva potvrzující úspěšné odhlášení.

Shrnutí

Máme odkaz pro přihlášení a také odhlášení uživatele. K ověření jsme použili vestavěný authentikátor a přihlašovací údaje máme v konfiguračním souboru, jelikož se jedná o jednoduchou testovací aplikaci. Také jsme zabezpečili editační formuláře, takže přidávat a editovat příspěvky mohou pouze přihlášení uživatelé.