Commit | Line | Data |
d442cc9f |
1 | =head1 NAME |
2 | |
3ab6187c |
3 | Catalyst::Manual::Tutorial::05_Authentication - Catalyst Tutorial - Chapter 5: Authentication |
d442cc9f |
4 | |
5 | |
6 | =head1 OVERVIEW |
7 | |
4b4d3884 |
8 | This is B<Chapter 5 of 10> for the Catalyst tutorial. |
d442cc9f |
9 | |
10 | L<Tutorial Overview|Catalyst::Manual::Tutorial> |
11 | |
12 | =over 4 |
13 | |
14 | =item 1 |
15 | |
3ab6187c |
16 | L<Introduction|Catalyst::Manual::Tutorial::01_Intro> |
d442cc9f |
17 | |
18 | =item 2 |
19 | |
3ab6187c |
20 | L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics> |
d442cc9f |
21 | |
22 | =item 3 |
23 | |
3ab6187c |
24 | L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics> |
d442cc9f |
25 | |
26 | =item 4 |
27 | |
3ab6187c |
28 | L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD> |
d442cc9f |
29 | |
30 | =item 5 |
31 | |
3ab6187c |
32 | B<05_Authentication> |
d442cc9f |
33 | |
34 | =item 6 |
35 | |
3ab6187c |
36 | L<Authorization|Catalyst::Manual::Tutorial::06_Authorization> |
d442cc9f |
37 | |
38 | =item 7 |
39 | |
3ab6187c |
40 | L<Debugging|Catalyst::Manual::Tutorial::07_Debugging> |
d442cc9f |
41 | |
42 | =item 8 |
43 | |
3ab6187c |
44 | L<Testing|Catalyst::Manual::Tutorial::08_Testing> |
d442cc9f |
45 | |
46 | =item 9 |
47 | |
3ab6187c |
48 | L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD> |
d442cc9f |
49 | |
3533daff |
50 | =item 10 |
d442cc9f |
51 | |
3ab6187c |
52 | L<Appendices|Catalyst::Manual::Tutorial::10_Appendices> |
d442cc9f |
53 | |
3533daff |
54 | =back |
2d0526d1 |
55 | |
2d0526d1 |
56 | |
d442cc9f |
57 | =head1 DESCRIPTION |
58 | |
905a3a26 |
59 | Now that we finally have a simple yet functional application, we can |
60 | focus on providing authentication (with authorization coming next in |
4b4d3884 |
61 | Chapter 6). |
d442cc9f |
62 | |
4b4d3884 |
63 | This chapter of the tutorial is divided into two main sections: 1) basic, |
d442cc9f |
64 | cleartext authentication and 2) hash-based authentication. |
65 | |
66 | You can checkout the source code for this example from the catalyst |
67 | subversion repository as per the instructions in |
3ab6187c |
68 | L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>. |
d442cc9f |
69 | |
fbbb9084 |
70 | |
d442cc9f |
71 | =head1 BASIC AUTHENTICATION |
72 | |
73 | This section explores how to add authentication logic to a Catalyst |
74 | application. |
75 | |
76 | |
77 | =head2 Add Users and Roles to the Database |
78 | |
79 | First, we add both user and role information to the database (we will |
80 | add the role information here although it will not be used until the |
4b4d3884 |
81 | authorization section, Chapter 6). Create a new SQL script file by opening |
d442cc9f |
82 | C<myapp02.sql> in your editor and insert: |
83 | |
c5b13dd3 |
84 | PRAGMA foreign_keys = ON; |
d442cc9f |
85 | -- |
3b1fa91b |
86 | -- Add user and role tables, along with a many-to-many join table |
d442cc9f |
87 | -- |
3b1fa91b |
88 | CREATE TABLE user ( |
d442cc9f |
89 | id INTEGER PRIMARY KEY, |
90 | username TEXT, |
91 | password TEXT, |
92 | email_address TEXT, |
93 | first_name TEXT, |
94 | last_name TEXT, |
95 | active INTEGER |
96 | ); |
3b1fa91b |
97 | CREATE TABLE role ( |
d442cc9f |
98 | id INTEGER PRIMARY KEY, |
99 | role TEXT |
100 | ); |
3b1fa91b |
101 | CREATE TABLE user_role ( |
b66dd084 |
102 | user_id INTEGER REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE, |
103 | role_id INTEGER REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE, |
d442cc9f |
104 | PRIMARY KEY (user_id, role_id) |
105 | ); |
106 | -- |
107 | -- Load up some initial test data |
108 | -- |
3b1fa91b |
109 | INSERT INTO user VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1); |
110 | INSERT INTO user VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1); |
111 | INSERT INTO user VALUES (3, 'test03', 'mypass', 't03@na.com', 'No', 'Go', 0); |
112 | INSERT INTO role VALUES (1, 'user'); |
113 | INSERT INTO role VALUES (2, 'admin'); |
114 | INSERT INTO user_role VALUES (1, 1); |
115 | INSERT INTO user_role VALUES (1, 2); |
116 | INSERT INTO user_role VALUES (2, 1); |
117 | INSERT INTO user_role VALUES (3, 1); |
d442cc9f |
118 | |
119 | Then load this into the C<myapp.db> database with the following command: |
120 | |
121 | $ sqlite3 myapp.db < myapp02.sql |
122 | |
444d6b27 |
123 | |
d442cc9f |
124 | =head2 Add User and Role Information to DBIC Schema |
125 | |
3533daff |
126 | Although we could manually edit the DBIC schema information to include |
127 | the new tables added in the previous step, let's use the C<create=static> |
128 | option on the DBIC model helper to do most of the work for us: |
d442cc9f |
129 | |
acbd7bdd |
130 | $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \ |
b66dd084 |
131 | create=static components=TimeStamp dbi:SQLite:myapp.db \ |
132 | on_connect_do="PRAGMA foreign_keys = ON" |
1390ef0e |
133 | exists "/root/dev/MyApp/script/../lib/MyApp/Model" |
134 | exists "/root/dev/MyApp/script/../t" |
135 | Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ... |
136 | Schema dump completed. |
137 | exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm" |
138 | $ |
acbd7bdd |
139 | $ ls lib/MyApp/Schema/Result |
3b1fa91b |
140 | Author.pm BookAuthor.pm Book.pm Role.pm User.pm UserRole.pm |
d442cc9f |
141 | |
905a3a26 |
142 | Notice how the helper has added three new table-specific result source |
acbd7bdd |
143 | files to the C<lib/MyApp/Schema/Result> directory. And, more |
905a3a26 |
144 | importantly, even if there were changes to the existing result source |
145 | files, those changes would have only been written above the C<# DO NOT |
191dee29 |
146 | MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited |
3533daff |
147 | enhancements would have been preserved. |
d442cc9f |
148 | |
3b1fa91b |
149 | Speaking of "hand-editted enhancements," we should now add |
905a3a26 |
150 | relationship information to the three new result source files. Edit |
151 | each of these files and add the following information between the C<# |
3533daff |
152 | DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>: |
d442cc9f |
153 | |
3b1fa91b |
154 | C<lib/MyApp/Schema/Result/User.pm>: |
d442cc9f |
155 | |
b66dd084 |
156 | |
3533daff |
157 | # many_to_many(): |
158 | # args: |
159 | # 1) Name of relationship, DBIC will create accessor with this name |
905a3a26 |
160 | # 2) Name of has_many() relationship this many_to_many() is shortcut for |
161 | # 3) Name of belongs_to() relationship in model class of has_many() above |
3533daff |
162 | # You must already have the has_many() defined to use a many_to_many(). |
b66dd084 |
163 | __PACKAGE__->many_to_many(roles => 'user_roles', 'role'); |
d442cc9f |
164 | |
3533daff |
165 | |
b66dd084 |
166 | The code for this update is obviously very similar to the edits we made to the |
167 | C<Book> and C<Author> classes created in Chapter 3. |
3533daff |
168 | |
636ba9f7 |
169 | Note that we do not need to make any change to the |
170 | C<lib/MyApp/Schema.pm> schema file. It simply tells DBIC to load all |
171 | of the Result Class and ResultSet Class files it finds in below the |
172 | C<lib/MyApp/Schema> directory, so it will automatically pick up our |
acbd7bdd |
173 | new table information. |
d442cc9f |
174 | |
175 | |
176 | =head2 Sanity-Check Reload of Development Server |
177 | |
905a3a26 |
178 | We aren't ready to try out the authentication just yet; we only want |
179 | to do a quick check to be sure our model loads correctly. Press |
180 | C<Ctrl-C> to kill the previous server instance (if it's still running) |
3533daff |
181 | and restart it: |
d442cc9f |
182 | |
183 | $ script/myapp_server.pl |
184 | |
185 | Look for the three new model objects in the startup debug output: |
186 | |
187 | ... |
188 | .-------------------------------------------------------------------+----------. |
189 | | Class | Type | |
190 | +-------------------------------------------------------------------+----------+ |
191 | | MyApp::Controller::Books | instance | |
192 | | MyApp::Controller::Root | instance | |
d0496197 |
193 | | MyApp::Model::DB | instance | |
194 | | MyApp::Model::DB::Author | class | |
3b1fa91b |
195 | | MyApp::Model::DB::Book | class | |
196 | | MyApp::Model::DB::BookAuthor | class | |
197 | | MyApp::Model::DB::Role | class | |
198 | | MyApp::Model::DB::User | class | |
199 | | MyApp::Model::DB::UserRole | class | |
d442cc9f |
200 | | MyApp::View::TT | instance | |
201 | '-------------------------------------------------------------------+----------' |
202 | ... |
203 | |
acbd7bdd |
204 | Again, notice that your "Result Class" classes have been "re-loaded" |
3533daff |
205 | by Catalyst under C<MyApp::Model>. |
d442cc9f |
206 | |
207 | |
208 | =head2 Include Authentication and Session Plugins |
209 | |
905a3a26 |
210 | Edit C<lib/MyApp.pm> and update it as follows (everything below |
3533daff |
211 | C<StackTrace> is new): |
d442cc9f |
212 | |
acbd7bdd |
213 | # Load plugins |
2a6eb5f9 |
214 | use Catalyst qw/ |
215 | -Debug |
3b1fa91b |
216 | ConfigLoader |
217 | Static::Simple |
efdaddec |
218 | |
3b1fa91b |
219 | StackTrace |
efdaddec |
220 | |
3b1fa91b |
221 | Authentication |
efdaddec |
222 | |
3b1fa91b |
223 | Session |
224 | Session::Store::FastMmap |
225 | Session::State::Cookie |
226 | /; |
d442cc9f |
227 | |
636ba9f7 |
228 | B<Note:> As discussed in MoreCatalystBasics, different versions of |
444d6b27 |
229 | C<Catalyst::Devel> have used a variety of methods to load the plugins, |
230 | but we are going to use the current Catalyst 5.8X practice of putting |
231 | them on the C<use Catalyst> line. |
94d8da41 |
232 | |
905a3a26 |
233 | The C<Authentication> plugin supports Authentication while the |
234 | C<Session> plugins are required to maintain state across multiple HTTP |
235 | requests. |
6d0971ad |
236 | |
905a3a26 |
237 | Note that the only required Authentication class is the main one. This |
238 | is a change that occurred in version 0.09999_01 of the |
239 | C<Authentication> plugin. You B<do not need> to specify a particular |
240 | Authentication::Store or Authentication::Credential plugin. Instead, |
241 | indicate the Store and Credential you want to use in your application |
6d0971ad |
242 | configuration (see below). |
243 | |
3b1fa91b |
244 | Make sure you include the additional plugins as new dependencies in |
245 | the Makefile.PL file something like this: |
246 | |
e12f8011 |
247 | requires 'Catalyst::Plugin::Authentication'; |
248 | requires 'Catalyst::Plugin::Session'; |
249 | requires 'Catalyst::Plugin::Session::Store::FastMmap'; |
250 | requires 'Catalyst::Plugin::Session::State::Cookie'; |
3b1fa91b |
251 | |
905a3a26 |
252 | Note that there are several options for |
253 | L<Session::Store|Catalyst::Plugin::Session::Store> |
e12f8011 |
254 | |
255 | (L<Session::Store::Memcached|Catalyst::Plugin::Session::Store::Memcached> or |
256 | L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap> is |
257 | generally a good choice if you are on Unix; try |
905a3a26 |
258 | L<Session::Store::File|Catalyst::Plugin::Session::Store::File> if you |
259 | are on Win32) -- consult |
260 | L<Session::Store|Catalyst::Plugin::Session::Store> and its subclasses |
3533daff |
261 | for additional information and options (for example to use a database- |
262 | backed session store). |
d442cc9f |
263 | |
264 | |
265 | =head2 Configure Authentication |
266 | |
3b1fa91b |
267 | There are a variety of ways to provide configuration information to |
efdaddec |
268 | L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>. |
269 | Here we will use |
270 | L<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB> |
271 | because it automatically sets a reasonable set of defaults for us. Open |
272 | C<lib/MyApp.pm> and place the following text above the call to |
273 | C<__PACKAGE__-E<gt>setup();>: |
274 | |
275 | # Configure SimpleDB Authentication |
276 | __PACKAGE__->config->{'Plugin::Authentication'} = { |
277 | default => { |
278 | class => 'SimpleDB', |
3b1fa91b |
279 | user_model => 'DB::User', |
efdaddec |
280 | password_type => 'clear', |
281 | }, |
282 | }; |
283 | |
284 | We could have placed this configuration in C<myapp.conf>, but placing |
285 | it in C<lib/MyApp.pm> is probably a better place since it's not likely |
286 | something that users of your application will want to change during |
c3cf3bc3 |
287 | deployment (or you could use a mixture: leave C<class> and |
288 | C<user_model> defined in C<lib/MyApp.pm> as we show above, but place |
289 | C<password_type> in C<myapp.conf> to allow the type of password to be |
290 | easily modified during deployment). We will stick with putting |
291 | all of the authentication-related configuration in C<lib/MyApp.pm> |
292 | for the tutorial, but if you wish to use C<myapp.conf>, just convert |
293 | to the following code: |
294 | |
295 | <Plugin::Authentication> |
c3cf3bc3 |
296 | <default> |
43707053 |
297 | password_type clear |
3b1fa91b |
298 | user_model DB::User |
c3cf3bc3 |
299 | class SimpleDB |
300 | </default> |
301 | </Plugin::Authentication> |
302 | |
303 | B<TIP:> Here is a short script that will dump the contents of |
304 | C<MyApp->config> to L<Config::General|Config::General> format in |
305 | C<myapp.conf>: |
306 | |
307 | $ perl -Ilib -e 'use MyApp; use Config::General; |
308 | Config::General->new->save_file("myapp.conf", MyApp->config);' |
d442cc9f |
309 | |
c4fa597d |
310 | B<NOTE:> Because we are using SimpleDB along with a database layout |
311 | that complies with its default assumptions, we don't need to specify |
312 | the names of the columns where our username and password information |
313 | is stored (hence, the "Simple" part of "SimpleDB"). That being said, |
87236f03 |
314 | SimpleDB lets you specify that type of information if you need to. |
c4fa597d |
315 | Take a look at |
316 | C<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB> |
317 | for details. |
318 | |
1390ef0e |
319 | |
d442cc9f |
320 | =head2 Add Login and Logout Controllers |
321 | |
322 | Use the Catalyst create script to create two stub controller files: |
323 | |
324 | $ script/myapp_create.pl controller Login |
325 | $ script/myapp_create.pl controller Logout |
326 | |
636ba9f7 |
327 | You could easily use a single controller here. For example, you could |
328 | have a C<User> controller with both C<login> and C<logout> actions. |
329 | Remember, Catalyst is designed to be very flexible, and leaves such |
fbbb9084 |
330 | matters up to you, the designer and programmer. |
d442cc9f |
331 | |
636ba9f7 |
332 | Then open C<lib/MyApp/Controller/Login.pm>, locate the |
333 | C<sub index :Path :Args(0)> method (or C<sub index : Private> if you |
334 | are using an older version of Catalyst) that was automatically |
335 | inserted by the helpers when we created the Login controller above, |
fbbb9084 |
336 | and update the definition of C<sub index> to match: |
d442cc9f |
337 | |
338 | =head2 index |
efdaddec |
339 | |
d442cc9f |
340 | Login logic |
efdaddec |
341 | |
d442cc9f |
342 | =cut |
efdaddec |
343 | |
ae492862 |
344 | sub index :Path :Args(0) { |
d442cc9f |
345 | my ($self, $c) = @_; |
efdaddec |
346 | |
d442cc9f |
347 | # Get the username and password from form |
ab0bd0bb |
348 | my $username = $c->request->params->{username}; |
349 | my $password = $c->request->params->{password}; |
efdaddec |
350 | |
d442cc9f |
351 | # If the username and password values were found in form |
ab0bd0bb |
352 | if ($username && $password) { |
d442cc9f |
353 | # Attempt to log the user in |
905a3a26 |
354 | if ($c->authenticate({ username => $username, |
5fefca35 |
355 | password => $password } )) { |
d442cc9f |
356 | # If successful, then let them use the application |
0416017e |
357 | $c->response->redirect($c->uri_for( |
358 | $c->controller('Books')->action_for('list'))); |
d442cc9f |
359 | return; |
360 | } else { |
361 | # Set an error message |
362 | $c->stash->{error_msg} = "Bad username or password."; |
363 | } |
ab0bd0bb |
364 | } else { |
365 | # Set an error message |
366 | $c->stash->{error_msg} = "Empty username or password."; |
d442cc9f |
367 | } |
efdaddec |
368 | |
d442cc9f |
369 | # If either of above don't work out, send to the login page |
370 | $c->stash->{template} = 'login.tt2'; |
371 | } |
372 | |
3b1fa91b |
373 | Be sure to remove the C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Login in Login.');> |
374 | line of the C<sub index>. |
375 | |
d442cc9f |
376 | This controller fetches the C<username> and C<password> values from the |
905a3a26 |
377 | login form and attempts to authenticate the user. If successful, it |
378 | redirects the user to the book list page. If the login fails, the user |
379 | will stay at the login page and receive an error message. If the |
380 | C<username> and C<password> values are not present in the form, the |
f632e28b |
381 | user will be taken to the empty login form. |
d442cc9f |
382 | |
636ba9f7 |
383 | Note that we could have used something like "C<sub default :Path>", |
384 | however, it is generally recommended (partly for historical reasons, |
385 | and partly for code clarity) only to use C<default> in |
386 | C<MyApp::Controller::Root>, and then mainly to generate the 404 not |
85d49fb6 |
387 | found page for the application. |
ae492862 |
388 | |
fbbb9084 |
389 | Instead, we are using "C<sub somename :Path :Args(0) {...}>" here to |
905a3a26 |
390 | specifically match the URL C</login>. C<Path> actions (aka, "literal |
391 | actions") create URI matches relative to the namespace of the |
392 | controller where they are defined. Although C<Path> supports |
393 | arguments that allow relative and absolute paths to be defined, here |
394 | we use an empty C<Path> definition to match on just the name of the |
395 | controller itself. The method name, C<index>, is arbitrary. We make |
ae492862 |
396 | the match even more specific with the C<:Args(0)> action modifier -- |
905a3a26 |
397 | this forces the match on I<only> C</login>, not |
d442cc9f |
398 | C</login/somethingelse>. |
399 | |
905a3a26 |
400 | Next, update the corresponding method in |
3533daff |
401 | C<lib/MyApp/Controller/Logout.pm> to match: |
d442cc9f |
402 | |
403 | =head2 index |
efdaddec |
404 | |
d442cc9f |
405 | Logout logic |
efdaddec |
406 | |
d442cc9f |
407 | =cut |
efdaddec |
408 | |
ae492862 |
409 | sub index :Path :Args(0) { |
d442cc9f |
410 | my ($self, $c) = @_; |
efdaddec |
411 | |
d442cc9f |
412 | # Clear the user's state |
413 | $c->logout; |
efdaddec |
414 | |
d442cc9f |
415 | # Send the user to the starting point |
416 | $c->response->redirect($c->uri_for('/')); |
417 | } |
418 | |
905a3a26 |
419 | As with the login controller, be sure to delete the |
14e5ed66 |
420 | C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Logout in Logout.');> |
d442cc9f |
421 | line of the C<sub index>. |
422 | |
423 | |
424 | =head2 Add a Login Form TT Template Page |
425 | |
426 | Create a login form by opening C<root/src/login.tt2> and inserting: |
427 | |
428 | [% META title = 'Login' %] |
efdaddec |
429 | |
d442cc9f |
430 | <!-- Login form --> |
8a7c5151 |
431 | <form method="post" action="[% c.uri_for('/login') %]"> |
d442cc9f |
432 | <table> |
433 | <tr> |
434 | <td>Username:</td> |
435 | <td><input type="text" name="username" size="40" /></td> |
436 | </tr> |
437 | <tr> |
438 | <td>Password:</td> |
439 | <td><input type="password" name="password" size="40" /></td> |
440 | </tr> |
441 | <tr> |
442 | <td colspan="2"><input type="submit" name="submit" value="Submit" /></td> |
443 | </tr> |
444 | </table> |
445 | </form> |
446 | |
447 | |
448 | =head2 Add Valid User Check |
449 | |
450 | We need something that provides enforcement for the authentication |
451 | mechanism -- a I<global> mechanism that prevents users who have not |
452 | passed authentication from reaching any pages except the login page. |
444d6b27 |
453 | This is generally done via an C<auto> action/method in |
454 | C<lib/MyApp/Controller/Root.pm>. |
d442cc9f |
455 | |
456 | Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert |
457 | the following method: |
458 | |
459 | =head2 auto |
efdaddec |
460 | |
d442cc9f |
461 | Check if there is a user and, if not, forward to login page |
efdaddec |
462 | |
d442cc9f |
463 | =cut |
efdaddec |
464 | |
d442cc9f |
465 | # Note that 'auto' runs after 'begin' but before your actions and that |
905a3a26 |
466 | # 'auto's "chain" (all from application path to most specific class are run) |
d442cc9f |
467 | # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info. |
ddfbd850 |
468 | sub auto :Private { |
d442cc9f |
469 | my ($self, $c) = @_; |
efdaddec |
470 | |
d442cc9f |
471 | # Allow unauthenticated users to reach the login page. This |
191dee29 |
472 | # allows unauthenticated users to reach any action in the Login |
d442cc9f |
473 | # controller. To lock it down to a single action, we could use: |
474 | # if ($c->action eq $c->controller('Login')->action_for('index')) |
905a3a26 |
475 | # to only allow unauthenticated access to the 'index' action we |
d442cc9f |
476 | # added above. |
477 | if ($c->controller eq $c->controller('Login')) { |
478 | return 1; |
479 | } |
efdaddec |
480 | |
d442cc9f |
481 | # If a user doesn't exist, force login |
482 | if (!$c->user_exists) { |
483 | # Dump a log message to the development server debug output |
484 | $c->log->debug('***Root::auto User not found, forwarding to /login'); |
485 | # Redirect the user to the login page |
486 | $c->response->redirect($c->uri_for('/login')); |
487 | # Return 0 to cancel 'post-auto' processing and prevent use of application |
488 | return 0; |
489 | } |
efdaddec |
490 | |
d442cc9f |
491 | # User found, so return 1 to continue with processing after this 'auto' |
492 | return 1; |
493 | } |
494 | |
636ba9f7 |
495 | As discussed in |
3ab6187c |
496 | L<Catalyst::Manual::Tutorial::03_MoreCatalystBasics/CREATE A CATALYST CONTROLLER>, |
636ba9f7 |
497 | every C<auto> method from the application/root controller down to the |
498 | most specific controller will be called. By placing the |
499 | authentication enforcement code inside the C<auto> method of |
500 | C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be |
501 | called for I<every> request that is received by the entire |
0416017e |
502 | application. |
d442cc9f |
503 | |
504 | |
505 | =head2 Displaying Content Only to Authenticated Users |
506 | |
507 | Let's say you want to provide some information on the login page that |
508 | changes depending on whether the user has authenticated yet. To do |
509 | this, open C<root/src/login.tt2> in your editor and add the following |
510 | lines to the bottom of the file: |
511 | |
acbd7bdd |
512 | ... |
d442cc9f |
513 | <p> |
514 | [% |
905a3a26 |
515 | # This code illustrates how certain parts of the TT |
d442cc9f |
516 | # template will only be shown to users who have logged in |
517 | %] |
8a7c5151 |
518 | [% IF c.user_exists %] |
519 | Please Note: You are already logged in as '[% c.user.username %]'. |
520 | You can <a href="[% c.uri_for('/logout') %]">logout</a> here. |
d442cc9f |
521 | [% ELSE %] |
522 | You need to log in to use this application. |
523 | [% END %] |
524 | [%# |
525 | Note that this whole block is a comment because the "#" appears |
905a3a26 |
526 | immediate after the "[%" (with no spaces in between). Although it |
527 | can be a handy way to temporarily "comment out" a whole block of |
528 | TT code, it's probably a little too subtle for use in "normal" |
d442cc9f |
529 | comments. |
530 | %] |
3533daff |
531 | </p> |
d442cc9f |
532 | |
533 | Although most of the code is comments, the middle few lines provide a |
534 | "you are already logged in" reminder if the user returns to the login |
535 | page after they have already authenticated. For users who have not yet |
536 | authenticated, a "You need to log in..." message is displayed (note the |
537 | use of an IF-THEN-ELSE construct in TT). |
538 | |
539 | |
540 | =head2 Try Out Authentication |
541 | |
542 | Press C<Ctrl-C> to kill the previous server instance (if it's still |
543 | running) and restart it: |
544 | |
545 | $ script/myapp_server.pl |
546 | |
636ba9f7 |
547 | B<IMPORTANT NOTE:> If you are having issues with authentication on |
548 | Internet Explorer, be sure to check the system clocks on both your |
549 | server and client machines. Internet Explorer is very picky about |
acbd7bdd |
550 | timestamps for cookies. You can quickly sync a Debian system by |
551 | installing the "ntpdate" package: |
552 | |
553 | sudo aptitude -y install ntpdate |
554 | |
555 | And then run the following command: |
25ed8f40 |
556 | |
acbd7bdd |
557 | sudo ntpdate-debian |
d442cc9f |
558 | |
acbd7bdd |
559 | Or, depending on your firewall configuration: |
560 | |
561 | sudo ntpdate-debian -u |
562 | |
636ba9f7 |
563 | Note: NTP can be a little more finicky about firewalls because it uses |
acbd7bdd |
564 | UDP vs. the more common TCP that you see with most Internet protocols. |
565 | Worse case, you might have to manually set the time on your development |
566 | box instead of using NTP. |
1390ef0e |
567 | |
636ba9f7 |
568 | Now trying going to L<http://localhost:3000/books/list> and you should |
569 | be redirected to the login page, hitting Shift+Reload or Ctrl+Reload |
570 | if necessary (the "You are already logged in" message should I<not> |
571 | appear -- if it does, click the C<logout> button and try again). Note |
572 | the C<***Root::auto User not found...> debug message in the |
573 | development server output. Enter username C<test01> and password |
1390ef0e |
574 | C<mypass>, and you should be taken to the Book List page. |
d442cc9f |
575 | |
576 | Open C<root/src/books/list.tt2> and add the following lines to the |
3533daff |
577 | bottom (below the closing </table> tag): |
d442cc9f |
578 | |
579 | <p> |
8a7c5151 |
580 | <a href="[% c.uri_for('/login') %]">Login</a> |
0416017e |
581 | <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a> |
d442cc9f |
582 | </p> |
583 | |
905a3a26 |
584 | Reload your browser and you should now see a "Login" and "Create" links |
585 | at the bottom of the page (as mentioned earlier, you can update template |
586 | files without reloading the development server). Click the first link |
587 | to return to the login page. This time you I<should> see the "You are |
d442cc9f |
588 | already logged in" message. |
589 | |
590 | Finally, click the C<You can logout here> link on the C</login> page. |
591 | You should stay at the login page, but the message should change to "You |
592 | need to log in to use this application." |
593 | |
594 | |
595 | =head1 USING PASSWORD HASHES |
596 | |
efdaddec |
597 | In this section we increase the security of our system by converting |
598 | from cleartext passwords to SHA-1 password hashes that include a |
599 | random "salt" value to make them extremely difficult to crack with |
600 | dictionary and "rainbow table" attacks. |
d442cc9f |
601 | |
602 | B<Note:> This section is optional. You can skip it and the rest of the |
603 | tutorial will function normally. |
604 | |
fbbb9084 |
605 | Be aware that even with the techniques shown in this section, the browser |
d442cc9f |
606 | still transmits the passwords in cleartext to your application. We are |
607 | just avoiding the I<storage> of cleartext passwords in the database by |
efdaddec |
608 | using a salted SHA-1 hash. If you are concerned about cleartext passwords |
d442cc9f |
609 | between the browser and your application, consider using SSL/TLS, made |
efdaddec |
610 | easy with the Catalyst plugin Catalyst::Plugin:RequireSSL. |
d442cc9f |
611 | |
612 | |
efdaddec |
613 | =head2 Re-Run the DBIC::Schema Model Helper to Include DBIx::Class::EncodedColumn |
d442cc9f |
614 | |
efdaddec |
615 | Next, we can re-run the model helper to have it include |
616 | L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> in all of the |
617 | Result Classes it generates for us. Simply use the same command we |
618 | saw in Chapters 3 and 4, but add C<,EncodedColumn> to the C<components> |
619 | argument: |
d442cc9f |
620 | |
efdaddec |
621 | $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \ |
b66dd084 |
622 | create=static components=TimeStamp,EncodedColumn dbi:SQLite:myapp.db \ |
623 | on_connect_do="PRAGMA foreign_keys = ON" |
d442cc9f |
624 | |
efdaddec |
625 | If you then open one of the Result Classes, you will see that it |
626 | includes EncodedColumn in the C<load_components> line. Take a look at |
3b1fa91b |
627 | C<lib/MyApp/Schema/Result/User.pm> since that's the main class where we |
efdaddec |
628 | want to use hashed and salted passwords: |
629 | |
e12f8011 |
630 | __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn"); |
efdaddec |
631 | |
632 | |
633 | =head2 Modify the "password" Column to Use EncodedColumn |
634 | |
3b1fa91b |
635 | Open the file C<lib/MyApp/Schema/Result/User.pm> and enter the following |
efdaddec |
636 | text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above |
637 | the closing "1;": |
638 | |
639 | # Have the 'password' column use a SHA-1 hash and 10-character salt |
640 | # with hex encoding; Generate the 'check_password" method |
641 | __PACKAGE__->add_columns( |
642 | 'password' => { |
643 | data_type => "TEXT", |
644 | size => undef, |
645 | encode_column => 1, |
646 | encode_class => 'Digest', |
647 | encode_args => {salt_length => 10}, |
648 | encode_check_method => 'check_password', |
649 | }, |
650 | ); |
651 | |
652 | This redefines the automatically generated definition for the password |
653 | fields at the top of the Result Class file to now use EncodedColumn |
654 | logic (C<encoded_column> is set to 1). C<encode_class> can be set to |
655 | either C<Digest> to use |
656 | L<DBIx::Class::EncodedColumn::Digest|DBIx::Class::EncodedColumn::Digest>, |
657 | or C<Crypt::Eksblowfish::Bcrypt> for |
658 | L<DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt|DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt>. |
659 | C<encode_args> is then used to customize the type of Digest you |
660 | selected. Here we only specified the size of the salt to use, but |
661 | we could have also modified the hashing algorithm ('SHA-256' is |
662 | the default) and the format to use ('base64' is the default, but |
663 | 'hex' and 'binary' are other options). To use these, you could |
664 | change the C<encode_args> to something like: |
665 | |
666 | encode_args => {algorithm => 'SHA-1', |
667 | format => 'hex', |
668 | salt_length => 10}, |
669 | |
670 | |
671 | =head2 Load Hashed Passwords in the Database |
672 | |
673 | Next, let's create a quick script to load some hashed and salted passwords |
674 | into the C<password> column of our C<users> table. Open the file |
675 | C<set_hashed_passwords.pl> in your editor and enter the following text: |
676 | |
677 | #!/usr/bin/perl |
678 | |
679 | use strict; |
680 | use warnings; |
681 | |
682 | use MyApp::Schema; |
683 | |
684 | my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db'); |
685 | |
3b1fa91b |
686 | my @users = $schema->resultset('User')->all; |
efdaddec |
687 | |
688 | foreach my $user (@users) { |
689 | $user->password('mypass'); |
690 | $user->update; |
691 | } |
692 | |
693 | EncodedColumn lets us simple call C<$user->check_password($password)> |
694 | to see if the user has supplied the correct password, or, as we show |
695 | above, call C<$user->update($new_password)> to update the hashed |
696 | password stored for this user. |
697 | |
698 | Then run the following command: |
699 | |
2a6eb5f9 |
700 | $ DBIC_TRACE=1 perl -Ilib set_hashed_passwords.pl |
efdaddec |
701 | |
c12b0d35 |
702 | We had to use the C<-Ilib> argument to tell perl to look under the |
efdaddec |
703 | C<lib> directory for our C<MyApp::Schema> model. |
704 | |
2a6eb5f9 |
705 | The DBIC_TRACE output should show that the update worked: |
706 | |
707 | $ DBIC_TRACE=1 perl -Ilib set_hashed_passwords.pl |
cc0ef55e |
708 | SELECT me.id, me.username, me.password, me.email_address, |
709 | me.first_name, me.last_name, me.active FROM user me: |
710 | UPDATE user SET password = ? WHERE ( id = ? ): |
711 | 'oXiyAcGOjowz7ISUhpIm1IrS8AxSZ9r4jNjpX9VnVeQmN6GRtRKTz', '1' |
712 | UPDATE user SET password = ? WHERE ( id = ? ): |
713 | 'PmyEPrkB8EGwvaF/DvJm7LIfxoZARjv8ygFIR7pc1gEA1OfwHGNzs', '2' |
714 | UPDATE user SET password = ? WHERE ( id = ? ): |
715 | 'h7CS1Fm9UCs4hjcbu2im0HumaHCJUq4Uriac+SQgdUMUfFSoOrz3c', '3' |
2a6eb5f9 |
716 | |
717 | But we can further confirm our actions by dumping the users table: |
efdaddec |
718 | |
3b1fa91b |
719 | $ sqlite3 myapp.db "select * from user" |
efdaddec |
720 | 1|test01|38d3974fa9e9263099f7bc2574284b2f55473a9bM=fwpX2NR8|t01@na.com|Joe|Blow|1 |
721 | 2|test02|6ed8586587e53e0d7509b1cfed5df08feadc68cbMJlnPyPt0I|t02@na.com|Jane|Doe|1 |
722 | 3|test03|af929a151340c6aed4d54d7e2651795d1ad2e2f7UW8dHoGv9z|t03@na.com|No|Go|0 |
723 | |
724 | As you can see, the passwords are much harder to steal from the |
444d6b27 |
725 | database (not only are the hashes stored, but every hash is different |
726 | even though the passwords are the same because of the added "salt" |
727 | value). Also note that this demonstrates how to use a DBIx::Class |
efdaddec |
728 | model outside of your web application -- a very useful feature in many |
729 | situations. |
730 | |
731 | |
732 | =head2 Enable Hashed and Salted Passwords |
733 | |
cc0ef55e |
734 | Edit C<lib/MyApp.pm> and update it to match the following text (the |
735 | only change is to the C<password_type> field): |
efdaddec |
736 | |
737 | # Configure SimpleDB Authentication |
738 | __PACKAGE__->config->{'Plugin::Authentication'} = { |
739 | default => { |
740 | class => 'SimpleDB', |
3b1fa91b |
741 | user_model => 'DB::User', |
efdaddec |
742 | password_type => 'self_check', |
743 | }, |
744 | }; |
745 | |
746 | The use of C<self_check> will cause |
747 | Catalyst::Plugin::Authentication::Store::DBIC to call the |
748 | C<check_password> method we enabled on our C<password> columns. |
d442cc9f |
749 | |
1390ef0e |
750 | |
d442cc9f |
751 | =head2 Try Out the Hashed Passwords |
752 | |
753 | Press C<Ctrl-C> to kill the previous server instance (if it's still |
754 | running) and restart it: |
755 | |
756 | $ script/myapp_server.pl |
757 | |
758 | You should now be able to go to L<http://localhost:3000/books/list> and |
fbbb9084 |
759 | login as before. When done, click the "logout" link on the login page |
d442cc9f |
760 | (or point your browser at L<http://localhost:3000/logout>). |
761 | |
d442cc9f |
762 | |
763 | =head1 USING THE SESSION FOR FLASH |
764 | |
4b4d3884 |
765 | As discussed in the previous chapter of the tutorial, C<flash> allows |
766 | you to set variables in a way that is very similar to C<stash>, but it |
767 | will remain set across multiple requests. Once the value is read, it |
768 | is cleared (unless reset). Although C<flash> has nothing to do with |
769 | authentication, it does leverage the same session plugins. Now that |
770 | those plugins are enabled, let's go back and update the "delete and |
771 | redirect with query parameters" code seen at the end of the L<Basic |
3ab6187c |
772 | CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD> chapter of the tutorial to |
4b4d3884 |
773 | take advantage of C<flash>. |
d442cc9f |
774 | |
775 | First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete> |
3533daff |
776 | to match the following (everything after the model search line of code |
777 | has changed): |
d442cc9f |
778 | |
905a3a26 |
779 | =head2 delete |
efdaddec |
780 | |
d442cc9f |
781 | Delete a book |
efdaddec |
782 | |
d442cc9f |
783 | =cut |
efdaddec |
784 | |
fbbb9084 |
785 | sub delete :Chained('object') :PathPart('delete') :Args(0) { |
786 | my ($self, $c) = @_; |
efdaddec |
787 | |
fbbb9084 |
788 | # Use the book object saved by 'object' and delete it along |
789 | # with related 'book_authors' entries |
790 | $c->stash->{object}->delete; |
efdaddec |
791 | |
d442cc9f |
792 | # Use 'flash' to save information across requests until it's read |
793 | $c->flash->{status_msg} = "Book deleted"; |
efdaddec |
794 | |
3533daff |
795 | # Redirect the user back to the list page |
0416017e |
796 | $c->response->redirect($c->uri_for($self->action_for('list'))); |
d442cc9f |
797 | } |
798 | |
1390ef0e |
799 | Next, open C<root/src/wrapper.tt2> and update the TT code to pull from |
d442cc9f |
800 | flash vs. the C<status_msg> query parameter: |
801 | |
1390ef0e |
802 | ... |
d442cc9f |
803 | <div id="content"> |
1390ef0e |
804 | [%# Status and error messages %] |
805 | <span class="message">[% status_msg || c.flash.status_msg %]</span> |
806 | <span class="error">[% error_msg %]</span> |
807 | [%# This is where TT will stick all of your template's contents. -%] |
808 | [% content %] |
809 | </div><!-- end content --> |
810 | ... |
905a3a26 |
811 | |
636ba9f7 |
812 | Although the sample above only shows the C<content> div, leave the |
cc0ef55e |
813 | rest of the file intact -- the only change we made to replace |
814 | "|| c.request.params.status_msg" with "c.flash.status_msg" in the |
815 | C<< <span class="message"> >> line. |
d442cc9f |
816 | |
817 | |
818 | =head2 Try Out Flash |
819 | |
636ba9f7 |
820 | Restart the development server, log in, and then point your browser to |
821 | L<http://localhost:3000/books/url_create/Test/1/4> to create an extra |
822 | several books. Click the "Return to list" link and delete one of the |
823 | "Test" books you just added. The C<flash> mechanism should retain our |
3533daff |
824 | "Book deleted" status message across the redirect. |
d442cc9f |
825 | |
826 | B<NOTE:> While C<flash> will save information across multiple requests, |
827 | I<it does get cleared the first time it is read>. In general, this is |
828 | exactly what you want -- the C<flash> message will get displayed on |
829 | the next screen where it's appropriate, but it won't "keep showing up" |
830 | after that first time (unless you reset it). Please refer to |
831 | L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional |
832 | information. |
833 | |
1390ef0e |
834 | |
3533daff |
835 | =head2 Switch To Flash-To-Stash |
836 | |
636ba9f7 |
837 | Although the a use of flash above works well, the |
1390ef0e |
838 | C<status_msg || c.flash.status_msg> statement is a little ugly. A nice |
905a3a26 |
839 | alternative is to use the C<flash_to_stash> feature that automatically |
1390ef0e |
840 | copies the content of flash to stash. This makes your controller |
905a3a26 |
841 | and template code work regardless of where it was directly access, a |
fbbb9084 |
842 | forward, or a redirect. To enable C<flash_to_stash>, you can either |
905a3a26 |
843 | set the value in C<lib/MyApp.pm> by changing the default |
3533daff |
844 | C<__PACKAGE__-E<gt>config> setting to something like: |
845 | |
846 | __PACKAGE__->config( |
efdaddec |
847 | name => 'MyApp', |
da59dbea |
848 | session => { flash_to_stash => 1 }, |
3533daff |
849 | ); |
850 | |
45d511e0 |
851 | B<or> add the following to C<myapp.conf>: |
3533daff |
852 | |
45d511e0 |
853 | <session> |
854 | flash_to_stash 1 |
855 | </session> |
3533daff |
856 | |
905a3a26 |
857 | The C<__PACKAGE__-E<gt>config> option is probably preferable here |
858 | since it's not something you will want to change at runtime without it |
3533daff |
859 | possibly breaking some of your code. |
860 | |
1390ef0e |
861 | Then edit C<root/src/wrapper.tt2> and change the C<status_msg> line |
862 | to match the following: |
3533daff |
863 | |
864 | <span class="message">[% status_msg %]</span> |
865 | |
866 | Restart the development server and go to |
905a3a26 |
867 | L<http://localhost:3000/books/list> in your browser. Delete another |
3533daff |
868 | of the "Test" books you added in the previous step. Flash should still |
869 | maintain the status message across the redirect even though you are no |
8a7c5151 |
870 | longer explicitly accessing C<c.flash>. |
3533daff |
871 | |
d442cc9f |
872 | |
873 | =head1 AUTHOR |
874 | |
875 | Kennedy Clark, C<hkclark@gmail.com> |
876 | |
877 | Please report any errors, issues or suggestions to the author. The |
878 | most recent version of the Catalyst Tutorial can be found at |
59884771 |
879 | L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>. |
d442cc9f |
880 | |
45c7830f |
881 | Copyright 2006-2008, Kennedy Clark, under Creative Commons License |
95674086 |
882 | (L<http://creativecommons.org/licenses/by-sa/3.0/us/>). |