forked from nette/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauthentication.texy
180 lines (122 loc) Β· 6.62 KB
/
authentication.texy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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:
```neon .{file:config/common.neon}
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 .{file:app/UI/Sign/SignPresenter.php}
<?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`
```latte .{file:app/UI/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.
```php .{file:app/UI/Sign/SignPresenter.php}
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() |api:Nette\Security\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|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|application:presenters#life-cycle-of-presenter]. This redirects non-logged-in users to the login form.
```php .{file:app/UI/Edit/EditPresenter.php}
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:
```latte
<a n:href="Edit:create" n:if="$user->isLoggedIn()">Create post</a>
```
this is a shortcut for (do not confuse it with `tag-if`):
```latte
{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.
```latte .{file:app/UI/@layout.latte}
...
<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.
```php .{file:app/UI/Sign/SignPresenter.php}
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.
.[note]
Here you can read more about [user login |security:authentication] and [authorization |security:authorization].
{{priority: -1}}
{{sitename: Nette Quickstart}}