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