Authentication

Nette provides you with guidelines 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\IAuthenticator 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 small blog, we can use the SimpleAuthenticator which is bundled with Nette. It provides password-based authentication and the usernames and passwords are stored in config file. Add a security section to your config.neon (and don't forget to change the password):

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

Nette will automatically create a service called authenticator in the DI container.

You can read more about Dependency Injection here, and about Configuration here

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\Presenters;

use Nette;
use Nette\Application\UI\Form;


class SignPresenter extends Nette\Application\UI\Presenter
{
    protected function createComponentSignInForm()
    {
        $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.

View

The form will be rendered in the template app/presenters/templates/Sign/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.

public function signInFormSucceeded(Form $form, Nette\Utils\ArrayHash $values)
{
    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.');
    }
}

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 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.

Post Form

First of all, let's secure the form for creating new posts. It's defined in PostPresenter and rendered in app/presenters/templates/Post/create.latte. Our first goal is not to allow the user to view the page if he's not logged in.

Presenter Views

Let's create an action method actionCreate that will redirect the user to the sign in form and require authentication if the user is not logged in.

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

We should also secure the edit view, so simply add those three lines there too.

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

An unauthenticated user cannot 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/presenters/templates/Homepage/default.latte, and it should be visible only if the user is logged in.

We can hide it using n:macro 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="Post:create" n:if="$user->loggedIn">Create post</a>

this is a shortcut for (do not confuse it with tag-if)

{if $user->loggedIn}<a n:href="Post:create">Create post</a>{/if}

You should hide the edit link located in app/presenters/templates/Post/show.latte in a similar fashion.

Form Handlers

The last, and the most important thing is to secure the form handler. Because components are reusable, they can be rendered in several views. And because they can be rendered in several views, they can also be submitted from any such view, even the ones that do not really exist. This means, even if the view create is not rendered, by tweaking the url, a user can still submit the form (and add/edit posts).

It can be prevented with a simple if that we will add at the beginning of postFormSucceeded

public function postFormSucceeded($form)
{
    if (!$this->getUser()->isLoggedIn()) {
        $this->error('You need to log in to create or edit posts');
    }

It's simple as that, but you shall never forget to do this as it's really crucial for securing your app.

Hey, but how do we get to the login page? There is no link pointing to it. Let's add one in the app/presenters/templates/@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="Homepage:">Homepage</a></li>
    {if $user->loggedIn}
        <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()
{
    $this->getUser()->logout();
    $this->flashMessage('You have been signed out.');
    $this->redirect('Homepage:');
}

It just calls the logout() method and then shows a nice message to the user.

Summary

There is a Sign in link pointing to a new presenter, which asks the user for his credentials and authenticates him. We used SimpleAuthenticator and configured the usernames and passwords in config file, because it was a very easy way and we don't need more users at the moment. We also secured all required actions and forms, so that only logged in users can add new posts or edit existing ones.