Authentication
Nette provides a way to implement authentication on our sites, but it doesn't force any specific approach. The implementation
is entirely up to us. Nette includes the Nette\Security\Authenticator
interface, which requires only one method,
authenticate
, to verify the user in any way we choose.
There are many ways a user can be authenticated. The most common method is password-based authentication (where the user provides their name or email and a password), but other methods exist. You might be familiar with “Login with Facebook” buttons or logging in via Google/Twitter/GitHub on some sites. With Nette, we can have any login method, or even combine them. It's entirely our decision.
Typically, we would write our own authenticator. However, for this simple blog, we'll use the built-in authenticator that logs
in based on a username and password stored in the configuration file. This is suitable for testing purposes. Let's add the
following security
section to the configuration file config/common.neon
:
security:
users:
admin: secret # user 'admin', password 'secret'
Nette automatically creates a service for this authenticator in the DI container.
Sign-In Form
Now that authentication is set up, we need to create a user interface for logging in. Let's create a new presenter named
SignPresenter
that will:
- display a login form (with username and password fields)
- authenticate the user when the form is submitted
- provide a sign-out option
We'll start with the login form. We already know how forms work in presenters. Let's create the SignPresenter
and
write the createComponentSignInForm
method. It should look something like this:
<?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', '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 are fields for the username and password.
Template
The form will be rendered in the in.latte
template:
{block content}
<h1 n:block=title>Sign in</h1>
{control signInForm}
Login Callback
Next, we'll add the callback method for logging in the user, which will be called immediately after the form is successfully submitted.
The callback simply takes the username and password entered by the user and passes them to the authenticator. After successful login, we redirect 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 User::login() method throws an exception if the username and password do not match the credentials in the configuration file. As we know, this could result in a Tracy red screen or, in production mode, a server error message. We don't want that. Therefore, we catch the exception and add a nice, user-friendly error message to the form.
When a form error occurs, the page with the form is re-rendered, and a message appears above the form informing the user that they entered the wrong username or password.
Securing Presenters
We will secure the form for adding and editing posts, which is defined in the EditPresenter
. The goal is to
prevent users who are not logged in from accessing the page.
We'll create a startup()
method, which runs immediately at the beginning of the presenter life cycle. This method will
redirect non-logged-in users to the login form.
public function startup(): void
{
parent::startup();
if (!$this->getUser()->isLoggedIn()) {
$this->redirect('Sign:in');
}
}
Hiding Links
An unauthorized user can no longer see the create or edit pages, but they can still see the links pointing to
them. We should hide these as well. One such link is in the app/Presentation/Home/default.latte
template and should
only be visible to logged-in users.
We can hide it using an n:attribute called n:if
. If the condition is false
, the entire
<a>
tag, including its content, will not be displayed.
<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a>
which is a shortcut for the following notation (do not confuse it with tag-if
):
{if $user->isLoggedIn()}<a n:href="Edit:create">Create post</a>{/if}
Hide the edit link in the app/Presentation/Post/show.latte
template in the same way.
Sign-in Link
How do we actually get to the sign-in page? There's no link leading to it. Let's add one to the @layout.latte
template. Try to find a suitable place – it can be almost anywhere.
...
<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 logged in, the “Sign in” link is displayed. Otherwise, the “Sign out” link is shown. We'll also add
this action to SignPresenter
.
Since we redirect the user immediately after signing out, no template is needed. The sign-out action looks like this:
public function actionOut(): void
{
$this->getUser()->logout();
$this->flashMessage('You have been signed out.');
$this->redirect('Home:');
}
It simply calls the logout()
method and then displays a confirmation message to the user.
Summary
We have links for signing in and signing out the user. We used the built-in authenticator for authentication, and the login credentials are in the configuration file, as this is a simple test application. We also secured the editing forms so that only logged-in users can add and edit posts.
You can read more about user login and authorization here.