1Catalyst::Manual::TutorUisaelr::C0o5n_tCArauittbahuletynestdti:cP:aeMtrailnounDa(ol3c:)u:mTeunttoartiiaoln::05_Authentication(3)
2
3
4

NAME

6       Catalyst::Manual::Tutorial::05_Authentication - Catalyst Tutorial -
7       Chapter 5: Authentication
8

OVERVIEW

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

DESCRIPTION

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

BASIC AUTHENTICATION

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

USING PASSWORD HASHES

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

USING THE SESSION FOR FLASH

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

AUTHOR

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)
Impressum