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       Source code for the tutorial in included in the /home/catalyst/Final
43       directory of the Tutorial Virtual machine (one subdirectory per
44       chapter).  There are also instructions for downloading the code in
45       Catalyst::Manual::Tutorial::01_Intro.
46

BASIC AUTHENTICATION

48       This section explores how to add authentication logic to a Catalyst
49       application.
50
51   Add Users and Roles to the Database
52       First, we add both user and role information to the database (we will
53       add the role information here although it will not be used until the
54       authorization section, Chapter 6).  Create a new SQL script file by
55       opening myapp02.sql in your editor and insert:
56
57           --
58           -- Add users and role tables, along with a many-to-many join table
59           --
60           PRAGMA foreign_keys = ON;
61           CREATE TABLE users (
62               id            INTEGER PRIMARY KEY,
63               username      TEXT,
64               password      TEXT,
65               email_address TEXT,
66               first_name    TEXT,
67               last_name     TEXT,
68               active        INTEGER
69           );
70           CREATE TABLE role (
71               id   INTEGER PRIMARY KEY,
72               role TEXT
73           );
74           CREATE TABLE user_role (
75               user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
76               role_id INTEGER REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE,
77               PRIMARY KEY (user_id, role_id)
78           );
79           --
80           -- Load up some initial test data
81           --
82           INSERT INTO users VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe',  'Blow', 1);
83           INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe',  1);
84           INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', 'No',   'Go',   0);
85           INSERT INTO role VALUES (1, 'user');
86           INSERT INTO role VALUES (2, 'admin');
87           INSERT INTO user_role VALUES (1, 1);
88           INSERT INTO user_role VALUES (1, 2);
89           INSERT INTO user_role VALUES (2, 1);
90           INSERT INTO user_role VALUES (3, 1);
91
92       Then load this into the myapp.db database with the following command:
93
94           $ sqlite3 myapp.db < myapp02.sql
95
96   Add User and Role Information to DBIC Schema
97       Although we could manually edit the DBIC schema information to include
98       the new tables added in the previous step, let's use the
99       "create=static" option on the DBIC model helper to do most of the work
100       for us:
101
102           $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
103               create=static components=TimeStamp dbi:SQLite:myapp.db \
104               on_connect_do="PRAGMA foreign_keys = ON"
105            exists "/home/catalyst/dev/MyApp/script/../lib/MyApp/Model"
106            exists "/home/catalyst/dev/MyApp/script/../t"
107           Dumping manual schema for MyApp::Schema to directory /home/catalyst/dev/MyApp/script/../lib ...
108           Schema dump completed.
109            exists "/home/catalyst/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
110           $
111           $ ls lib/MyApp/Schema/Result
112           Author.pm  BookAuthor.pm  Book.pm  Role.pm  User.pm  UserRole.pm
113
114       Notice how the helper has added three new table-specific Result Source
115       files to the lib/MyApp/Schema/Result directory.  And, more importantly,
116       even if there were changes to the existing result source files, those
117       changes would have only been written above the "# DO NOT MODIFY THIS OR
118       ANYTHING ABOVE!" comment and your hand-edited enhancements would have
119       been preserved.
120
121       Speaking of "hand-edited enhancements," we should now add the
122       "many_to_many" relationship information to the User Result Source file.
123       As with the Book, BookAuthor, and Author files in Chapter 3,
124       DBIx::Class::Schema::Loader has automatically created the "has_many"
125       and "belongs_to" relationships for the new User, UserRole, and Role
126       tables. However, as a convenience for mapping Users to their assigned
127       roles (see Chapter 6), we will also manually add a "many_to_many"
128       relationship. Edit lib/MyApp/Schema/Result/User.pm add the following
129       information between the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!"
130       comment and the closing "1;":
131
132           # many_to_many():
133           #   args:
134           #     1) Name of relationship, DBIC will create accessor with this name
135           #     2) Name of has_many() relationship this many_to_many() is shortcut for
136           #     3) Name of belongs_to() relationship in model class of has_many() above
137           #   You must already have the has_many() defined to use a many_to_many().
138           __PACKAGE__->many_to_many(roles => 'user_roles', 'role');
139
140       The code for this update is obviously very similar to the edits we made
141       to the "Book" and "Author" classes created in Chapter 3 with one
142       exception: we only defined the "many_to_many" relationship in one
143       direction. Whereas we felt that we would want to map Authors to Books
144       AND Books to Authors, here we are only adding the convenience
145       "many_to_many" in the Users to Roles direction.
146
147       Note that we do not need to make any change to the lib/MyApp/Schema.pm
148       schema file.  It simply tells DBIC to load all of the Result Class and
149       ResultSet Class files it finds below the lib/MyApp/Schema directory, so
150       it will automatically pick up our new 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::HTML                                                 | 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::File
197               Session::State::Cookie
198           /;
199
200       Note: As discussed in Chapter 3, 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.9 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" you want to use.  Instead, indicate the
212       Store and Credential you want to use in your application configuration
213       (see 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::File';
221           requires 'Catalyst::Plugin::Session::State::Cookie';
222
223       Note that there are several options for Session::Store.
224       Session::Store::Memcached is generally a good choice if you are on
225       Unix.  If you are running on Windows Session::Store::File is fine.
226       Consult Session::Store and its subclasses for additional information
227       and options (for example to use a database-backed session store).
228
229   Configure Authentication
230       There are a variety of ways to provide configuration information to
231       Catalyst::Plugin::Authentication.  Here we will use
232       Catalyst::Authentication::Realm::SimpleDB because it automatically sets
233       a reasonable set of defaults for us.  (Note: the "SimpleDB" here has
234       nothing to do with the SimpleDB offered in Amazon's web services
235       offerings -- here we are only talking about a "simple" way to use your
236       DB as an authentication backend.)  Open lib/MyApp.pm and place the
237       following text above the call to "__PACKAGE__->setup();":
238
239           # Configure SimpleDB Authentication
240           __PACKAGE__->config(
241               'Plugin::Authentication' => {
242                   default => {
243                       class           => 'SimpleDB',
244                       user_model      => 'DB::User',
245                       password_type   => 'clear',
246                   },
247               },
248           );
249
250       We could have placed this configuration in myapp.conf, but placing it
251       in lib/MyApp.pm is probably a better place since it's not likely
252       something that users of your application will want to change during
253       deployment (or you could use a mixture: leave "class" and "user_model"
254       defined in lib/MyApp.pm as we show above, but place "password_type" in
255       myapp.conf to allow the type of password to be easily modified during
256       deployment).  We will stick with putting all of the authentication-
257       related configuration in lib/MyApp.pm for the tutorial, but if you wish
258       to use myapp.conf, just convert to the following code:
259
260           <Plugin::Authentication>
261               <default>
262                   password_type clear
263                   user_model    DB::User
264                   class         SimpleDB
265               </default>
266           </Plugin::Authentication>
267
268       TIP: Here is a short script that will dump the contents of
269       "MyApp->config" to Config::General format in myapp.conf:
270
271           $ CATALYST_DEBUG=0 perl -Ilib -e 'use MyApp; use Config::General;
272               Config::General->new->save_file("myapp.conf", MyApp->config);'
273
274       HOWEVER, if you try out the command above, be sure to delete the
275       "myapp.conf" command.  Otherwise, you will wind up with duplicate
276       configurations.
277
278       NOTE: Because we are using SimpleDB along with a database layout that
279       complies with its default assumptions: we don't need to specify the
280       names of the columns where our username and password information is
281       stored (hence, the "Simple" part of "SimpleDB").  That being said,
282       SimpleDB lets you specify that type of information if you need to.
283       Take a look at "Catalyst::Authentication::Realm::SimpleDB" for details.
284
285   Add Login and Logout Controllers
286       Use the Catalyst create script to create two stub controller files:
287
288           $ script/myapp_create.pl controller Login
289           $ script/myapp_create.pl controller Logout
290
291       You could easily use a single controller here.  For example, you could
292       have a "User" controller with both "login" and "logout" actions.
293       Remember, Catalyst is designed to be very flexible, and leaves such
294       matters up to you, the designer and programmer.
295
296       Then open lib/MyApp/Controller/Login.pm, and update the definition of
297       "sub index" to match:
298
299           =head2 index
300
301           Login logic
302
303           =cut
304
305           sub index :Path :Args(0) {
306               my ($self, $c) = @_;
307
308               # Get the username and password from form
309               my $username = $c->request->params->{username};
310               my $password = $c->request->params->{password};
311
312               # If the username and password values were found in form
313               if ($username && $password) {
314                   # Attempt to log the user in
315                   if ($c->authenticate({ username => $username,
316                                          password => $password  } )) {
317                       # If successful, then let them use the application
318                       $c->response->redirect($c->uri_for(
319                           $c->controller('Books')->action_for('list')));
320                       return;
321                   } else {
322                       # Set an error message
323                       $c->stash(error_msg => "Bad username or password.");
324                   }
325               } else {
326                   # Set an error message
327                   $c->stash(error_msg => "Empty username or password.")
328                       unless ($c->user_exists);
329               }
330
331               # If either of above don't work out, send to the login page
332               $c->stash(template => 'login.tt2');
333           }
334
335       This controller fetches the "username" and "password" values from the
336       login form and attempts to authenticate the user.  If successful, it
337       redirects the user to the book list page.  If the login fails, the user
338       will stay at the login page and receive an error message.  If the
339       "username" and "password" values are not present in the form, the user
340       will be taken to the empty login form.
341
342       Note that we could have used something like ""sub default :Path"",
343       however, it is generally recommended (partly for historical reasons,
344       and partly for code clarity) only to use "default" in
345       "MyApp::Controller::Root", and then mainly to generate the 404 not
346       found page for the application.
347
348       Instead, we are using ""sub somename :Path :Args(0) {...}"" here to
349       specifically match the URL "/login". "Path" actions (aka, "literal
350       actions") create URI matches relative to the namespace of the
351       controller where they are defined.  Although "Path" supports arguments
352       that allow relative and absolute paths to be defined, here we use an
353       empty "Path" definition to match on just the name of the controller
354       itself.  The method name, "index", is arbitrary. We make the match even
355       more specific with the :Args(0) action modifier -- this forces the
356       match on only "/login", not "/login/somethingelse".
357
358       Next, update the corresponding method in lib/MyApp/Controller/Logout.pm
359       to match:
360
361           =head2 index
362
363           Logout logic
364
365           =cut
366
367           sub index :Path :Args(0) {
368               my ($self, $c) = @_;
369
370               # Clear the user's state
371               $c->logout;
372
373               # Send the user to the starting point
374               $c->response->redirect($c->uri_for('/'));
375           }
376
377   Add a Login Form TT Template Page
378       Create a login form by opening root/src/login.tt2 and inserting:
379
380           [% META title = 'Login' %]
381
382           <!-- Login form -->
383           <form method="post" action="[% c.uri_for('/login') %]">
384               <table>
385                   <tr>
386                       <td>Username:</td>
387                       <td><input type="text" name="username" size="40" /></td>
388                   </tr>
389                   <tr>
390                       <td>Password:</td>
391                       <td><input type="password" name="password" size="40" /></td>
392                   </tr>
393                   <tr>
394                       <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
395                   </tr>
396               </table>
397           </form>
398
399   Add Valid User Check
400       We need something that provides enforcement for the authentication
401       mechanism -- a global mechanism that prevents users who have not passed
402       authentication from reaching any pages except the login page.  This is
403       generally done via an "auto" action/method in
404       lib/MyApp/Controller/Root.pm.
405
406       Edit the existing lib/MyApp/Controller/Root.pm class file and insert
407       the following method:
408
409           =head2 auto
410
411           Check if there is a user and, if not, forward to login page
412
413           =cut
414
415           # Note that 'auto' runs after 'begin' but before your actions and that
416           # 'auto's "chain" (all from application path to most specific class are run)
417           # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
418           sub auto :Private {
419               my ($self, $c) = @_;
420
421               # Allow unauthenticated users to reach the login page.  This
422               # allows unauthenticated users to reach any action in the Login
423               # controller.  To lock it down to a single action, we could use:
424               #   if ($c->action eq $c->controller('Login')->action_for('index'))
425               # to only allow unauthenticated access to the 'index' action we
426               # added above.
427               if ($c->controller eq $c->controller('Login')) {
428                   return 1;
429               }
430
431               # If a user doesn't exist, force login
432               if (!$c->user_exists) {
433                   # Dump a log message to the development server debug output
434                   $c->log->debug('***Root::auto User not found, forwarding to /login');
435                   # Redirect the user to the login page
436                   $c->response->redirect($c->uri_for('/login'));
437                   # Return 0 to cancel 'post-auto' processing and prevent use of application
438                   return 0;
439               }
440
441               # User found, so return 1 to continue with processing after this 'auto'
442               return 1;
443           }
444
445       As discussed in "CREATE A CATALYST CONTROLLER" in
446       Catalyst::Manual::Tutorial::03_MoreCatalystBasics, every "auto" method
447       from the application/root controller down to the most specific
448       controller will be called.  By placing the authentication enforcement
449       code inside the "auto" method of lib/MyApp/Controller/Root.pm (or
450       lib/MyApp.pm), it will be called for every request that is received by
451       the entire application.
452
453   Displaying Content Only to Authenticated Users
454       Let's say you want to provide some information on the login page that
455       changes depending on whether the user has authenticated yet.  To do
456       this, open root/src/login.tt2 in your editor and add the following
457       lines to the bottom of the file:
458
459           ...
460           <p>
461           [%
462               # This code illustrates how certain parts of the TT
463               # template will only be shown to users who have logged in
464           %]
465           [% IF c.user_exists %]
466               Please Note: You are already logged in as '[% c.user.username %]'.
467               You can <a href="[% c.uri_for('/logout') %]">logout</a> here.
468           [% ELSE %]
469               You need to log in to use this application.
470           [% END %]
471           [%#
472               Note that this whole block is a comment because the "#" appears
473               immediate after the "[%" (with no spaces in between).  Although it
474               can be a handy way to temporarily "comment out" a whole block of
475               TT code, it's probably a little too subtle for use in "normal"
476               comments.
477           %]
478           </p>
479
480       Although most of the code is comments, the middle few lines provide a
481       "you are already logged in" reminder if the user returns to the login
482       page after they have already authenticated.  For users who have not yet
483       authenticated, a "You need to log in..." message is displayed (note the
484       use of an IF-THEN-ELSE construct in TT).
485
486   Try Out Authentication
487       The development server should have reloaded each time we edited one of
488       the Controllers in the previous section. Now try going to
489       <http://localhost:3000/books/list> and you should be redirected to the
490       login page, hitting Shift+Reload or Ctrl+Reload if necessary (the "You
491       are already logged in" message should not appear -- if it does, click
492       the "logout" button and try again). Note the "***Root::auto User not
493       found..." debug message in the development server output. Enter
494       username "test01" and password "mypass", and you should be taken to the
495       Book List page.
496
497       IMPORTANT NOTE: If you are having issues with authentication on
498       Internet Explorer (or potentially other browsers), be sure to check the
499       system clocks on both your server and client machines.  Internet
500       Explorer is very picky about timestamps for cookies.  You can use the
501       "ntpq -p" command on the Tutorial Virtual Machine to check time sync
502       and/or use the following command to force a sync:
503
504           sudo ntpdate-debian
505
506       Or, depending on your firewall configuration, try it with "-u":
507
508           sudo ntpdate-debian -u
509
510       Note: NTP can be a little more finicky about firewalls because it uses
511       UDP vs. the more common TCP that you see with most Internet protocols.
512       Worse case, you might have to manually set the time on your development
513       box instead of using NTP.
514
515       Open root/src/books/list.tt2 and add the following lines to the bottom
516       (below the closing </table> tag):
517
518           ...
519           <p>
520               <a href="[% c.uri_for('/login') %]">Login</a>
521               <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
522           </p>
523
524       Reload your browser and you should now see a "Login" and "Create" links
525       at the bottom of the page (as mentioned earlier, you can update
526       template files without a development server reload).  Click the first
527       link to return to the login page.  This time you should see the "You
528       are already logged in" message.
529
530       Finally, click the "You can logout here" link on the "/login" page.
531       You should stay at the login page, but the message should change to
532       "You need to log in to use this application."
533

USING PASSWORD HASHES

535       In this section we increase the security of our system by converting
536       from cleartext passwords to SHA-1 password hashes that include a random
537       "salt" value to make them extremely difficult to crack, even with
538       dictionary and "rainbow table" attacks.
539
540       Note: This section is optional.  You can skip it and the rest of the
541       tutorial will function normally.
542
543       Be aware that even with the techniques shown in this section, the
544       browser still transmits the passwords in cleartext to your application.
545       We are just avoiding the storage of cleartext passwords in the database
546       by using a salted SHA-1 hash. If you are concerned about cleartext
547       passwords between the browser and your application, consider using
548       SSL/TLS, made easy with modules such as Catalyst::Plugin::RequireSSL
549       and Catalyst::ActionRole::RequireSSL.
550
551   Re-Run the DBIC::Schema Model Helper to Include
552       DBIx::Class::PassphraseColumn
553       Let's re-run the model helper to have it include
554       DBIx::Class::PassphraseColumn in all of the Result Classes it generates
555       for us.  Simply use the same command we saw in Chapters 3 and 4, but
556       add ",PassphraseColumn" to the "components" argument:
557
558           $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
559               create=static components=TimeStamp,PassphraseColumn dbi:SQLite:myapp.db \
560               on_connect_do="PRAGMA foreign_keys = ON"
561
562       If you then open one of the Result Classes, you will see that it
563       includes PassphraseColumn in the "load_components" line.  Take a look
564       at lib/MyApp/Schema/Result/User.pm since that's the main class where we
565       want to use hashed and salted passwords:
566
567           __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "PassphraseColumn");
568
569   Modify the "password" Column to Use PassphraseColumn
570       Open the file lib/MyApp/Schema/Result/User.pm and enter the following
571       text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above
572       the closing "1;":
573
574           # Have the 'password' column use a SHA-1 hash and 20-byte salt
575           # with RFC 2307 encoding; Generate the 'check_password" method
576           __PACKAGE__->add_columns(
577               'password' => {
578                   passphrase       => 'rfc2307',
579                   passphrase_class => 'SaltedDigest',
580                   passphrase_args  => {
581                       algorithm   => 'SHA-1',
582                       salt_random => 20,
583                   },
584                   passphrase_check_method => 'check_password',
585               },
586           );
587
588       This redefines the automatically generated definition for the password
589       fields at the top of the Result Class file to now use PassphraseColumn
590       logic, storing passwords in RFC 2307 format ("passphrase" is set to
591       "rfc2307").  "passphrase_class" can be set to the name of any
592       "Authen::Passphrase::*" class, such as "SaltedDigest" to use
593       Authen::Passphrase::SaltedDigest, or "BlowfishCrypt" to use
594       Authen::Passphrase::BlowfishCrypt.  "passphrase_args" is then used to
595       customize the passphrase class you selected. Here we specified the
596       digest algorithm to use as "SHA-1" and the size of the salt to use, but
597       we could have also specified any other option the selected passphrase
598       class supports.
599
600   Load Hashed Passwords in the Database
601       Next, let's create a quick script to load some hashed and salted
602       passwords into the "password" column of our "users" table.  Open the
603       file set_hashed_passwords.pl in your editor and enter the following
604       text:
605
606           #!/usr/bin/perl
607
608           use strict;
609           use warnings;
610
611           use MyApp::Schema;
612
613           my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db');
614
615           my @users = $schema->resultset('User')->all;
616
617           foreach my $user (@users) {
618               $user->password('mypass');
619               $user->update;
620           }
621
622       PassphraseColumn lets us simply call "$user->check_password($password)"
623       to see if the user has supplied the correct password, or, as we show
624       above, call "$user->update($new_password)" to update the hashed
625       password stored for this user.
626
627       Then run the following command:
628
629           $ DBIC_TRACE=1 perl -Ilib set_hashed_passwords.pl
630
631       We had to use the "-Ilib" argument to tell Perl to look under the lib
632       directory for our "MyApp::Schema" model.
633
634       The DBIC_TRACE output should show that the update worked:
635
636           $ DBIC_TRACE=1 perl -Ilib set_hashed_passwords.pl
637           SELECT me.id, me.username, me.password, me.email_address,
638           me.first_name, me.last_name, me.active FROM users me:
639           UPDATE users SET password = ? WHERE ( id = ? ):
640           '{SSHA}esgz64CpHMo8pMfgIIszP13ft23z/zio04aCwNdm0wc6MDeloMUH4g==', '1'
641           UPDATE users SET password = ? WHERE ( id = ? ):
642           '{SSHA}FpGhpCJus+Ea9ne4ww8404HH+hJKW/fW+bAv1v6FuRUy2G7I2aoTRQ==', '2'
643           UPDATE users SET password = ? WHERE ( id = ? ):
644           '{SSHA}ZyGlpiHls8qFBSbHr3r5t/iqcZE602XLMbkSVRRNl6rF8imv1abQVg==', '3'
645
646       But we can further confirm our actions by dumping the users table:
647
648           $ sqlite3 myapp.db "select * from users"
649           1|test01|{SSHA}esgz64CpHMo8pMfgIIszP13ft23z/zio04aCwNdm0wc6MDeloMUH4g==|t01@na.com|Joe|Blow|1
650           2|test02|{SSHA}FpGhpCJus+Ea9ne4ww8404HH+hJKW/fW+bAv1v6FuRUy2G7I2aoTRQ==|t02@na.com|Jane|Doe|1
651           3|test03|{SSHA}ZyGlpiHls8qFBSbHr3r5t/iqcZE602XLMbkSVRRNl6rF8imv1abQVg==|t03@na.com|No|Go|0
652
653       As you can see, the passwords are much harder to steal from the
654       database (not only are the hashes stored, but every hash is different
655       even though the passwords are the same because of the added "salt"
656       value).  Also note that this demonstrates how to use a DBIx::Class
657       model outside of your web application -- a very useful feature in many
658       situations.
659
660   Enable Hashed and Salted Passwords
661       Edit lib/MyApp.pm and update the config() section for
662       "Plugin::Authentication" it to match the following text (the only
663       change is to the "password_type" field):
664
665           # Configure SimpleDB Authentication
666           __PACKAGE__->config(
667               'Plugin::Authentication' => {
668                   default => {
669                       class           => 'SimpleDB',
670                       user_model      => 'DB::User',
671                       password_type   => 'self_check',
672                   },
673               },
674           );
675
676       The use of "self_check" will cause
677       Catalyst::Plugin::Authentication::Store::DBIx::Class to call the
678       "check_password" method we enabled on our "password" columns.
679
680   Try Out the Hashed Passwords
681       The development server should restart as soon as your save the
682       lib/MyApp.pm file in the previous section. You should now be able to go
683       to <http://localhost:3000/books/list> and login as before. When done,
684       click the "logout" link on the login page (or point your browser at
685       <http://localhost:3000/logout>).
686

USING THE SESSION FOR FLASH

688       As discussed in the previous chapter of the tutorial, "flash" allows
689       you to set variables in a way that is very similar to "stash", but it
690       will remain set across multiple requests.  Once the value is read, it
691       is cleared (unless reset).  Although "flash" has nothing to do with
692       authentication, it does leverage the same session plugins.  Now that
693       those plugins are enabled, let's go back and update the "delete and
694       redirect with query parameters" code seen at the end of the Basic CRUD
695       chapter of the tutorial to take advantage of "flash".
696
697       First, open lib/MyApp/Controller/Books.pm and modify "sub delete" to
698       match the following (everything after the model search line of code has
699       changed):
700
701           =head2 delete
702
703           Delete a book
704
705           =cut
706
707           sub delete :Chained('object') :PathPart('delete') :Args(0) {
708               my ($self, $c) = @_;
709
710               # Use the book object saved by 'object' and delete it along
711               # with related 'book_authors' entries
712               $c->stash->{object}->delete;
713
714               # Use 'flash' to save information across requests until it's read
715               $c->flash->{status_msg} = "Book deleted";
716
717               # Redirect the user back to the list page
718               $c->response->redirect($c->uri_for($self->action_for('list')));
719           }
720
721       Next, open root/src/wrapper.tt2 and update the TT code to pull from
722       flash vs. the "status_msg" query parameter:
723
724           ...
725           <div id="content">
726               [%# Status and error messages %]
727               <span class="message">[% status_msg || c.flash.status_msg %]</span>
728               <span class="error">[% error_msg %]</span>
729               [%# This is where TT will stick all of your template's contents. -%]
730               [% content %]
731           </div><!-- end content -->
732           ...
733
734       Although the sample above only shows the "content" div, leave the rest
735       of the file intact -- the only change we made to replace ""||
736       c.request.params.status_msg"" with ""c.flash.status_msg"" in the "<span
737       class="message">" line.
738
739   Try Out Flash
740       Authenticate using the login screen and then point your browser to
741       <http://localhost:3000/books/url_create/Test/1/4> to create an extra
742       several books.  Click the "Return to list" link and delete one of the
743       "Test" books you just added.  The "flash" mechanism should retain our
744       "Book deleted" status message across the redirect.
745
746       NOTE: While "flash" will save information across multiple requests, it
747       does get cleared the first time it is read.  In general, this is
748       exactly what you want -- the "flash" message will get displayed on the
749       next screen where it's appropriate, but it won't "keep showing up"
750       after that first time (unless you reset it).  Please refer to
751       Catalyst::Plugin::Session for additional information.
752
753       Note: There is also a "flash-to-stash" feature that will automatically
754       load the contents the contents of flash into stash, allowing us to use
755       the more typical "c.flash.status_msg" in our TT template in lieu of the
756       more verbose "status_msg || c.flash.status_msg" we used above.  Consult
757       Catalyst::Plugin::Session for additional information.
758
759   Switch To Catalyst::Plugin::StatusMessages
760       Although the query parameter technique we used in Chapter 4 and the
761       "flash" approach we used above will work in most cases, they both have
762       their drawbacks.  The query parameters can leave the status message on
763       the screen longer than it should (for example, if the user hits
764       refresh).  And "flash" can display the wrong message on the wrong
765       screen (flash just shows the message on the next page for that user...
766       if the user has multiple windows or tabs open, then the wrong one can
767       get the status message).
768
769       Catalyst::Plugin::StatusMessage is designed to address these
770       shortcomings.  It stores the messages in the user's session (so they
771       are available across multiple requests), but ties each status message
772       to a random token.  By passing this token across the redirect, we are
773       no longer relying on a potentially ambiguous "next request" like we do
774       with flash.  And, because the message is deleted the first time it's
775       displayed, the user can hit refresh and still only see the message a
776       single time (even though the URL may continue to reference the token,
777       it's only displayed the first time).  The use of "StatusMessage" or a
778       similar mechanism is recommended for all Catalyst applications.
779
780       To enable "StatusMessage", first edit lib/MyApp.pm and add
781       "StatusMessage" to the list of plugins:
782
783           use Catalyst qw/
784               -Debug
785               ConfigLoader
786               Static::Simple
787
788               StackTrace
789
790               Authentication
791
792               Session
793               Session::Store::File
794               Session::State::Cookie
795
796               StatusMessage
797           /;
798
799       Then edit lib/MyApp/Controller/Books.pm and modify the "delete" action
800       to match the following:
801
802           sub delete :Chained('object') :PathPart('delete') :Args(0) {
803               my ($self, $c) = @_;
804
805               # Saved the PK id for status_msg below
806               my $id = $c->stash->{object}->id;
807
808               # Use the book object saved by 'object' and delete it along
809               # with related 'book_authors' entries
810               $c->stash->{object}->delete;
811
812               # Redirect the user back to the list page
813               $c->response->redirect($c->uri_for($self->action_for('list'),
814                   {mid => $c->set_status_msg("Deleted book $id")}));
815           }
816
817       This uses the "set_status_msg" that the plugin added to $c to save the
818       message under a random token.  (If we wanted to save an error message,
819       we could have used "set_error_msg".)  Because "set_status_msg" and
820       "set_error_msg" both return the random token, we can assign that value
821       to the ""mid"" query parameter via "uri_for" as shown above.
822
823       Next, we need to make sure that the list page will load display the
824       message.  The easiest way to do this is to take advantage of the
825       chained dispatch we implemented in Chapter 4.  Edit
826       lib/MyApp/Controller/Books.pm again and update the "base" action to
827       match:
828
829           sub base :Chained('/') :PathPart('books') :CaptureArgs(0) {
830               my ($self, $c) = @_;
831
832               # Store the ResultSet in stash so it's available for other methods
833               $c->stash(resultset => $c->model('DB::Book'));
834
835               # Print a message to the debug log
836               $c->log->debug('*** INSIDE BASE METHOD ***');
837
838               # Load status messages
839               $c->load_status_msgs;
840           }
841
842       That way, anything that chains off "base" will automatically get any
843       status or error messages loaded into the stash.  Let's convert the
844       "list" action to take advantage of this.  Modify the method signature
845       for "list" from:
846
847           sub list :Local {
848
849       to:
850
851           sub list :Chained('base') :PathPart('list') :Args(0) {
852
853       Finally, let's clean up the status/error message code in our wrapper
854       template.  Edit root/src/wrapper.tt2 and change the "content" div to
855       match the following:
856
857           <div id="content">
858               [%# Status and error messages %]
859               <span class="message">[% status_msg %]</span>
860               <span class="error">[% error_msg %]</span>
861               [%# This is where TT will stick all of your template's contents. -%]
862               [% content %]
863           </div><!-- end content -->
864
865       Now go to <http://localhost:3000/books/list> in your browser. Delete
866       another of the "Test" books you added in the previous step.  You should
867       get redirection from the "delete" action back to the "list" action, but
868       with a "mid=########" message ID query parameter.  The screen should
869       say "Deleted book #" (where # is the PK id of the book you removed).
870       However, if you hit refresh in your browser, the status message is no
871       longer displayed  (even though the URL does still contain the message
872       ID token, it is ignored -- thereby keeping the state of our
873       status/error messages in sync with the users actions).
874
875       You can jump to the next chapter of the tutorial here: Authorization
876

AUTHOR

878       Kennedy Clark, "hkclark@gmail.com"
879
880       Feel free to contact the author for any errors or suggestions, but the
881       best way to report issues is via the CPAN RT Bug system at
882       <https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
883
884       Copyright 2006-2011, Kennedy Clark, under the Creative Commons
885       Attribution Share-Alike License Version 3.0
886       (<https://creativecommons.org/licenses/by-sa/3.0/us/>).
887
888
889
890perl v5.34.0                  Cata2l0y2s2t-:0:1M-a2n0ual::Tutorial::05_Authentication(3)
Impressum