Authentication
Nette provides you with guidelines on how to program authentication on your page, but it doesn't force you to do it any
particular way. The implementation is up to you. Nette has a Nette\Security\Authenticator
interface which forces you
to implement just a single method called authenticate
, which finds the user anyhow you want.
There are many ways how a user can authenticate himself. The most common way is password-based authentication (user provides his name or email and a password), but there are other means as well. You may be familiar with “Login with Facebook” buttons on many websites, or login via Google/Twitter/GitHub or any other site. With Nette, you can have any authentication method you want, or you can combine them. It's up to you.
Normally you would write your own authenticator, but for this simple little blog we'll use the built-in authenticator, which
authenticates based on a password and username stored in a configuration file. It's good for testing purposes. So we'll add the
following security section to the config/common.neon
configuration file:
security:
users:
admin: secret # user 'admin', password 'secret'
Nette will automatically create a service in the DI container.
Sign-In Form
We now have the backend part of authentication ready and we need to provide a user interface, through which the user would log in. Let's create a new presenter called SignPresenter, which will
- display a login form (asking for username and password)
- authenticate the user when the form is submitted
- provide log out action
Let's start with the login form. You already know how forms work in a presenter. Create the SignPresenter
and
method createComponentSignInForm
. It should look like this:
<?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', 'Username:')
->setRequired('Please enter your username.');
$form->addPassword('password', 'Password:')
->setRequired('Please enter your password.');
$form->addSubmit('send', 'Sign in');
$form->onSuccess[] = $this->signInFormSucceeded(...);
return $form;
}
}
There is an input for username and password.
Template
The form will be rendered in the template in.latte
{block content}
<h1 n:block=title>Sign in</h1>
{control signInForm}
Login Handler
We add also a form handler for signing in the user, that gets invoked right after the form is submitted.
The handler will just take the username and password the user entered and will pass it to the authenticator defined earlier. After the user has logged in, we will redirect him to the homepage.
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('Incorrect username or password.');
}
}
The method User::login() should throw an exception when the username or password doesn't match those we've defined earlier. As we already know, that would result in a Tracy red-screen, or, in production mode, a message informing about an internal server error. We wouldn't like that. That's why we catch the exception and add a nice and friendly error message to the form.
When the error occurs in the form, the page with the form will be rendered again, and above the form, there will be a nice message, informing the user that they have entered a wrong username or password.
Security of Presenters
We will secure a form for adding and editing posts. It is defined in the presenter EditPresenter
. The goal is to
prevent users who are not logged in from accessing the page.
We create a method startup()
that is started immediately at the beginning of the presenter life cycle. This redirects
non-logged-in users to the login form.
public function startup(): void
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
Hide Links
An unauthenticated user can no longer see the create nor edit page, but he can still see the links pointing to
them. Let's hide those as well. One such link is in app/UI/Home/default.latte
, and it should be visible only if the
user is logged in.
We can hide it using n:attribute called n:if
. If the statement inside it is false
, the whole
<a>
tag and it's contents will be not displayed:
<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a>
this is a shortcut for (do not confuse it with tag-if
):
{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if}
You should hide the edit link located in app/UI/Post/show.latte
in a similar fashion.
Login Form Link
Hey, but how do we get to the login page? There is no link pointing to it. Let's add one in the @layout.latte
template file. Try finding a nice place, it can be anywhere you like it the most.
...
<ul class="navig">
<li><a n:href="Home:">Home</a></li>
{if $user->isLoggedIn()}
<li><a n:href="Sign:out">Sign out</a></li>
{else}
<li><a n:href="Sign:in">Sign in</a></li>
{/if}
</ul>
...
If the user is not yet logged in, we will show the “Sign in” link. Otherwise, we will show the “Sign out” link. We add that action in SignPresenter.
The logout action looks like this, and because we redirect the user immediately, there is no need for a view template.
public function actionOut(): void
{
$this->getUser()->logout();
$this->flashMessage('You have been signed out.');
$this->redirect('Home:');
}
It just calls the logout()
method and then shows a nice message to the user.
Summary
We have a link to log in and also to log out the user. We have used the built-in authenticator for authentication and the login details are in the configuration file as this is a simple test application. We have also secured the edit forms so that only logged in users can add and edit posts.
Here you can read more about user login and authorization.