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
116       importantly, even if there were changes to the existing result source
117       files, those changes would have only been written above the "# DO NOT
118       MODIFY THIS OR ANYTHING ABOVE!" comment and your hand-edited
119       enhancements would have 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
148       "lib/MyApp/Schema.pm" schema file.  It simply tells DBIC to load all of
149       the Result Class and ResultSet Class files it finds below the
150       "lib/MyApp/Schema" directory, so it will automatically pick up our new
151       table information.
152
153   Sanity-Check of the Development Server Reload
154       We aren't ready to try out the authentication just yet; we only want to
155       do a quick check to be sure our model loads correctly. Assuming that
156       you are following along and using the "-r" option on "myapp_server.pl",
157       then the development server should automatically reload (if not, press
158       "Ctrl-C" to break out of the server if it's running and then enter
159       "script/myapp_server.pl" to start it). Look for the three new model
160       objects in the startup debug output:
161
162           ...
163            .-------------------------------------------------------------------+----------.
164           | Class                                                             | Type     |
165           +-------------------------------------------------------------------+----------+
166           | MyApp::Controller::Books                                          | instance |
167           | MyApp::Controller::Root                                           | instance |
168           | MyApp::Model::DB                                                  | instance |
169           | MyApp::Model::DB::Author                                          | class    |
170           | MyApp::Model::DB::Book                                            | class    |
171           | MyApp::Model::DB::BookAuthor                                      | class    |
172           | MyApp::Model::DB::Role                                            | class    |
173           | MyApp::Model::DB::User                                            | class    |
174           | MyApp::Model::DB::UserRole                                        | class    |
175           | MyApp::View::HTML                                                 | instance |
176           '-------------------------------------------------------------------+----------'
177           ...
178
179       Again, notice that your "Result Class" classes have been "re-loaded" by
180       Catalyst under "MyApp::Model".
181
182   Include Authentication and Session Plugins
183       Edit "lib/MyApp.pm" and update it as follows (everything below
184       "StackTrace" is new):
185
186           # Load plugins
187           use Catalyst qw/
188               -Debug
189               ConfigLoader
190               Static::Simple
191
192               StackTrace
193
194               Authentication
195
196               Session
197               Session::Store::File
198               Session::State::Cookie
199           /;
200
201       Note: As discussed in Chapter 3, different versions of
202       "Catalyst::Devel" have used a variety of methods to load the plugins,
203       but we are going to use the current Catalyst 5.9 practice of putting
204       them on the "use Catalyst" line.
205
206       The "Authentication" plugin supports Authentication while the "Session"
207       plugins are required to maintain state across multiple HTTP requests.
208
209       Note that the only required Authentication class is the main one. This
210       is a change that occurred in version 0.09999_01 of the Authentication
211       plugin. You do not need to specify a particular Authentication::Store
212       or "Authentication::Credential" you want to use.  Instead, indicate the
213       Store and Credential you want to use in your application configuration
214       (see below).
215
216       Make sure you include the additional plugins as new dependencies in the
217       Makefile.PL file something like this:
218
219           requires 'Catalyst::Plugin::Authentication';
220           requires 'Catalyst::Plugin::Session';
221           requires 'Catalyst::Plugin::Session::Store::File';
222           requires 'Catalyst::Plugin::Session::State::Cookie';
223
224       Note that there are several options for Session::Store.
225       Session::Store::Memcached is generally a good choice if you are on
226       Unix.  If you are running on Windows Session::Store::File is fine.
227       Consult Session::Store and its subclasses for additional information
228       and options (for example to use a database-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.  (Note: the "SimpleDB" here has
235       nothing to do with the SimpleDB offered in Amazon's web services
236       offerings -- here we are only talking about a "simple" way to use your
237       DB as an authentication backend.)  Open "lib/MyApp.pm" and place the
238       following text above the call to "__PACKAGE__->setup();":
239
240           # Configure SimpleDB Authentication
241           __PACKAGE__->config(
242               'Plugin::Authentication' => {
243                   default => {
244                       class           => 'SimpleDB',
245                       user_model      => 'DB::User',
246                       password_type   => 'clear',
247                   },
248               },
249           );
250
251       We could have placed this configuration in "myapp.conf", but placing it
252       in "lib/MyApp.pm" is probably a better place since it's not likely
253       something that users of your application will want to change during
254       deployment (or you could use a mixture: leave "class" and "user_model"
255       defined in "lib/MyApp.pm" as we show above, but place "password_type"
256       in "myapp.conf" to allow the type of password to be easily modified
257       during deployment).  We will stick with putting all of the
258       authentication-related configuration in "lib/MyApp.pm" for the
259       tutorial, but if you wish to use "myapp.conf", just convert to the
260       following code:
261
262           <Plugin::Authentication>
263               <default>
264                   password_type clear
265                   user_model    DB::User
266                   class         SimpleDB
267               </default>
268           </Plugin::Authentication>
269
270       TIP: Here is a short script that will dump the contents of
271       "MyApp-"config> to Config::General format in "myapp.conf":
272
273           $ CATALYST_DEBUG=0 perl -Ilib -e 'use MyApp; use Config::General;
274               Config::General->new->save_file("myapp.conf", MyApp->config);'
275
276       HOWEVER, if you try out the command above, be sure to delete the
277       "myapp.conf" command.  Otherwise, you will wind up with duplicate
278       configurations.
279
280       NOTE: Because we are using SimpleDB along with a database layout that
281       complies with its default assumptions: we don't need to specify the
282       names of the columns where our username and password information is
283       stored (hence, the "Simple" part of "SimpleDB").  That being said,
284       SimpleDB lets you specify that type of information if you need to.
285       Take a look at "Catalyst::Authentication::Realm::SimpleDB" for details.
286
287   Add Login and Logout Controllers
288       Use the Catalyst create script to create two stub controller files:
289
290           $ script/myapp_create.pl controller Login
291           $ script/myapp_create.pl controller Logout
292
293       You could easily use a single controller here.  For example, you could
294       have a "User" controller with both "login" and "logout" actions.
295       Remember, Catalyst is designed to be very flexible, and leaves such
296       matters up to you, the designer and programmer.
297
298       Then open "lib/MyApp/Controller/Login.pm", and update the definition of
299       "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                       unless ($c->user_exists);
331               }
332
333               # If either of above don't work out, send to the login page
334               $c->stash(template => 'login.tt2');
335           }
336
337       This controller fetches the "username" and "password" values from the
338       login form and attempts to authenticate the user.  If successful, it
339       redirects the user to the book list page.  If the login fails, the user
340       will stay at the login page and receive an error message.  If the
341       "username" and "password" values are not present in the form, the user
342       will be taken to the empty login form.
343
344       Note that we could have used something like ""sub default :Path"",
345       however, it is generally recommended (partly for historical reasons,
346       and partly for code clarity) only to use "default" in
347       "MyApp::Controller::Root", and then mainly to generate the 404 not
348       found page for the application.
349
350       Instead, we are using ""sub somename :Path :Args(0) {...}"" here to
351       specifically match the URL "/login". "Path" actions (aka, "literal
352       actions") create URI matches relative to the namespace of the
353       controller where they are defined.  Although "Path" supports arguments
354       that allow relative and absolute paths to be defined, here we use an
355       empty "Path" definition to match on just the name of the controller
356       itself.  The method name, "index", is arbitrary. We make the match even
357       more specific with the :Args(0) action modifier -- this forces the
358       match on only "/login", not "/login/somethingelse".
359
360       Next, update the corresponding method in
361       "lib/MyApp/Controller/Logout.pm" to match:
362
363           =head2 index
364
365           Logout logic
366
367           =cut
368
369           sub index :Path :Args(0) {
370               my ($self, $c) = @_;
371
372               # Clear the user's state
373               $c->logout;
374
375               # Send the user to the starting point
376               $c->response->redirect($c->uri_for('/'));
377           }
378
379   Add a Login Form TT Template Page
380       Create a login form by opening "root/src/login.tt2" and inserting:
381
382           [% META title = 'Login' %]
383
384           <!-- Login form -->
385           <form method="post" action="[% c.uri_for('/login') %]">
386             <table>
387               <tr>
388                 <td>Username:</td>
389                 <td><input type="text" name="username" size="40" /></td>
390               </tr>
391               <tr>
392                 <td>Password:</td>
393                 <td><input type="password" name="password" size="40" /></td>
394               </tr>
395               <tr>
396                 <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
397               </tr>
398             </table>
399           </form>
400
401   Add Valid User Check
402       We need something that provides enforcement for the authentication
403       mechanism -- a global mechanism that prevents users who have not passed
404       authentication from reaching any pages except the login page.  This is
405       generally done via an "auto" action/method in
406       "lib/MyApp/Controller/Root.pm".
407
408       Edit the existing "lib/MyApp/Controller/Root.pm" class file and insert
409       the following method:
410
411           =head2 auto
412
413           Check if there is a user and, if not, forward to login page
414
415           =cut
416
417           # Note that 'auto' runs after 'begin' but before your actions and that
418           # 'auto's "chain" (all from application path to most specific class are run)
419           # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
420           sub auto :Private {
421               my ($self, $c) = @_;
422
423               # Allow unauthenticated users to reach the login page.  This
424               # allows unauthenticated users to reach any action in the Login
425               # controller.  To lock it down to a single action, we could use:
426               #   if ($c->action eq $c->controller('Login')->action_for('index'))
427               # to only allow unauthenticated access to the 'index' action we
428               # added above.
429               if ($c->controller eq $c->controller('Login')) {
430                   return 1;
431               }
432
433               # If a user doesn't exist, force login
434               if (!$c->user_exists) {
435                   # Dump a log message to the development server debug output
436                   $c->log->debug('***Root::auto User not found, forwarding to /login');
437                   # Redirect the user to the login page
438                   $c->response->redirect($c->uri_for('/login'));
439                   # Return 0 to cancel 'post-auto' processing and prevent use of application
440                   return 0;
441               }
442
443               # User found, so return 1 to continue with processing after this 'auto'
444               return 1;
445           }
446
447       As discussed in "CREATE A CATALYST CONTROLLER" in
448       Catalyst::Manual::Tutorial::03_MoreCatalystBasics, every "auto" method
449       from the application/root controller down to the most specific
450       controller will be called.  By placing the authentication enforcement
451       code inside the "auto" method of "lib/MyApp/Controller/Root.pm" (or
452       "lib/MyApp.pm"), it will be called for every request that is received
453       by the entire application.
454
455   Displaying Content Only to Authenticated Users
456       Let's say you want to provide some information on the login page that
457       changes depending on whether the user has authenticated yet.  To do
458       this, open "root/src/login.tt2" in your editor and add the following
459       lines to the bottom of the file:
460
461           ...
462           <p>
463           [%
464              # This code illustrates how certain parts of the TT
465              # template will only be shown to users who have logged in
466           %]
467           [% IF c.user_exists %]
468               Please Note: You are already logged in as '[% c.user.username %]'.
469               You can <a href="[% c.uri_for('/logout') %]">logout</a> here.
470           [% ELSE %]
471               You need to log in to use this application.
472           [% END %]
473           [%#
474              Note that this whole block is a comment because the "#" appears
475              immediate after the "[%" (with no spaces in between).  Although it
476              can be a handy way to temporarily "comment out" a whole block of
477              TT code, it's probably a little too subtle for use in "normal"
478              comments.
479           %]
480           </p>
481
482       Although most of the code is comments, the middle few lines provide a
483       "you are already logged in" reminder if the user returns to the login
484       page after they have already authenticated.  For users who have not yet
485       authenticated, a "You need to log in..." message is displayed (note the
486       use of an IF-THEN-ELSE construct in TT).
487
488   Try Out Authentication
489       The development server should have reloaded each time we edited one of
490       the Controllers in the previous section. Now try going to
491       <http://localhost:3000/books/list> and you should be redirected to the
492       login page, hitting Shift+Reload or Ctrl+Reload if necessary (the "You
493       are already logged in" message should not appear -- if it does, click
494       the "logout" button and try again). Note the "***Root::auto User not
495       found..." debug message in the development server output. Enter
496       username "test01" and password "mypass", and you should be taken to the
497       Book List page.
498
499       IMPORTANT NOTE: If you are having issues with authentication on
500       Internet Explorer (or potentially other browsers), be sure to check the
501       system clocks on both your server and client machines.  Internet
502       Explorer is very picky about timestamps for cookies.  You can use the
503       "ntpq -p" command on the Tutorial Virtual Machine to check time sync
504       and/or use the following command to force a sync:
505
506           sudo ntpdate-debian
507
508       Or, depending on your firewall configuration, try it with "-u":
509
510           sudo ntpdate-debian -u
511
512       Note: NTP can be a little more finicky about firewalls because it uses
513       UDP vs. the more common TCP that you see with most Internet protocols.
514       Worse case, you might have to manually set the time on your development
515       box instead of using NTP.
516
517       Open "root/src/books/list.tt2" and add the following lines to the
518       bottom (below the closing </table> tag):
519
520           ...
521           <p>
522             <a href="[% c.uri_for('/login') %]">Login</a>
523             <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
524           </p>
525
526       Reload your browser and you should now see a "Login" and "Create" links
527       at the bottom of the page (as mentioned earlier, you can update
528       template files without a development server reload).  Click the first
529       link to return to the login page.  This time you should see the "You
530       are already logged in" message.
531
532       Finally, click the "You can logout here" link on the "/login" page.
533       You should stay at the login page, but the message should change to
534       "You need to log in to use this application."
535

USING PASSWORD HASHES

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

USING THE SESSION FOR FLASH

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

AUTHOR

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