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