Commit | Line | Data |
4d583dd8 |
1 | =head1 NAME |
2 | |
64ccd8a8 |
3 | Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 4: Authentication |
4d583dd8 |
4 | |
5 | |
6 | |
7 | =head1 OVERVIEW |
8 | |
9 | This is B<Part 4 of 9> for the Catalyst tutorial. |
10 | |
64ccd8a8 |
11 | L<Tutorial Overview|Catalyst::Manual::Tutorial> |
4d583dd8 |
12 | |
13 | =over 4 |
14 | |
15 | =item 1 |
16 | |
17 | L<Introduction|Catalyst::Manual::Tutorial::Intro> |
18 | |
19 | =item 2 |
20 | |
21 | L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics> |
22 | |
23 | =item 3 |
24 | |
64ccd8a8 |
25 | L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD> |
4d583dd8 |
26 | |
27 | =item 4 |
28 | |
29 | B<Authentication> |
30 | |
31 | =item 5 |
32 | |
33 | L<Authorization|Catalyst::Manual::Tutorial::Authorization> |
34 | |
35 | =item 6 |
36 | |
37 | L<Debugging|Catalyst::Manual::Tutorial::Debugging> |
38 | |
39 | =item 7 |
40 | |
41 | L<Testing|Catalyst::Manual::Tutorial::Testing> |
42 | |
43 | =item 8 |
44 | |
45 | L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD> |
46 | |
47 | =item 9 |
48 | |
49 | L<Appendicies|Catalyst::Manual::Tutorial::Appendicies> |
50 | |
51 | =back |
52 | |
53 | |
54 | |
55 | =head1 DESCRIPTION |
56 | |
64ccd8a8 |
57 | Now that we finally have a simple yet functional application, we can |
58 | focus on providing authentication (with authorization coming in Part 5). |
4d583dd8 |
59 | |
64ccd8a8 |
60 | This part of the tutorial is divided into two main sections: 1) basic, |
61 | cleartext authentication and 2) hash-based authentication. |
4d583dd8 |
62 | |
64ccd8a8 |
63 | B<TIP>: Note that all of the code for this part of the tutorial can be |
64 | pulled from the Catalyst Subversion repository in one step with the |
65 | following command: |
4d583dd8 |
66 | |
67 | svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@### |
68 | IMPORTANT: Does not work yet. Will be completed for final version. |
69 | |
70 | |
71 | |
72 | =head1 BASIC AUTHENTICATION |
73 | |
74 | This section explores how add authentication logic to a Catalyst application. |
75 | |
76 | |
77 | =head2 Add Users and Roles to the Database |
78 | |
64ccd8a8 |
79 | First, we add both user and role information to the database (we add the |
80 | role information here although it will not be used until the |
81 | authorization section, Part 5). Create a new SQL script file by opening |
82 | C<myapp02.sql> in your editor and insert: |
4d583dd8 |
83 | |
84 | -- |
85 | -- Add users and roles tables, along with a many-to-many join table |
86 | -- |
87 | CREATE TABLE users ( |
88 | id INTEGER PRIMARY KEY, |
89 | username TEXT, |
90 | password TEXT, |
91 | email_address TEXT, |
92 | first_name TEXT, |
93 | last_name TEXT, |
94 | active INTEGER |
95 | ); |
96 | CREATE TABLE roles ( |
97 | id INTEGER PRIMARY KEY, |
98 | role TEXT |
99 | ); |
100 | CREATE TABLE user_roles ( |
101 | user_id INTEGER, |
102 | role_id INTEGER, |
103 | PRIMARY KEY (user_id, role_id) |
104 | ); |
105 | -- |
106 | -- Load up some initial test data |
107 | -- |
108 | INSERT INTO users VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1); |
109 | INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1); |
110 | INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', 'No', 'Go', 0); |
111 | INSERT INTO roles VALUES (1, 'user'); |
112 | INSERT INTO roles VALUES (2, 'admin'); |
113 | INSERT INTO user_roles VALUES (1, 1); |
114 | INSERT INTO user_roles VALUES (1, 2); |
115 | INSERT INTO user_roles VALUES (2, 1); |
116 | INSERT INTO user_roles VALUES (3, 1); |
117 | |
118 | Then load this into the C<myapp.db> database with the following command: |
119 | |
120 | $ sqlite3 myapp.db < myapp02.sql |
121 | |
122 | |
123 | =head2 Add User and Role Information to Dbic Schema |
124 | |
64ccd8a8 |
125 | This step adds DBIC-based classes for the user-related database tables |
126 | (the role information will not be used until the Part 5): |
4d583dd8 |
127 | |
64ccd8a8 |
128 | Edit C<lib/MyAppDB.pm> and update the contents to match (only the |
129 | C<MyAppDB =E<gt> [qw/Book BookAuthor Author User UserRole Role/]> line |
130 | has changed): |
4d583dd8 |
131 | |
132 | package MyAppDB; |
133 | |
134 | =head1 NAME |
135 | |
136 | MyAppDB -- DBIC Schema Class |
137 | |
138 | =cut |
139 | |
140 | # Our schema needs to inherit from 'DBIx::Class::Schema' |
141 | use base qw/DBIx::Class::Schema/; |
142 | |
143 | # Need to load the DB Model classes here. |
144 | # You can use this syntax if you want: |
145 | # __PACKAGE__->load_classes(qw/Book BookAuthor Author User UserRole Role/); |
146 | # Also, if you simply want to load all of the classes in a directory |
147 | # of the same name as your schema class (as we do here) you can use: |
148 | # __PACKAGE__->load_classes(qw//); |
149 | # But the variation below is more flexible in that it can be used to |
150 | # load from multiple namespaces. |
151 | __PACKAGE__->load_classes({ |
152 | MyAppDB => [qw/Book BookAuthor Author User UserRole Role/] |
153 | }); |
154 | |
155 | 1; |
156 | |
157 | |
158 | =head2 Create New "Result Source Objects" |
159 | |
160 | Create the following three files with the content shown below. |
161 | |
162 | C<lib/MyAppDB/User.pm>: |
163 | |
164 | package MyAppDB::User; |
165 | |
166 | use base qw/DBIx::Class/; |
167 | |
168 | # Load required DBIC stuff |
169 | __PACKAGE__->load_components(qw/PK::Auto Core/); |
170 | # Set the table name |
171 | __PACKAGE__->table('users'); |
172 | # Set columns in table |
173 | __PACKAGE__->add_columns(qw/id username password email_address first_name last_name/); |
174 | # Set the primary key for the table |
175 | __PACKAGE__->set_primary_key('id'); |
176 | |
177 | # |
178 | # Set relationships: |
179 | # |
180 | |
181 | # has_many(): |
182 | # args: |
183 | # 1) Name of relationship, DBIC will create accessor with this name |
184 | # 2) Name of the model class referenced by this relationship |
185 | # 3) Column name in *foreign* table |
186 | __PACKAGE__->has_many(map_user_role => 'MyAppDB::UserRole', 'user_id'); |
187 | |
188 | |
189 | =head1 NAME |
190 | |
191 | MyAppDB::User - A model object representing a person with access to the system. |
192 | |
193 | =head1 DESCRIPTION |
194 | |
195 | This is an object that represents a row in the 'users' table of your application |
196 | database. It uses DBIx::Class (aka, DBIC) to do ORM. |
197 | |
198 | For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. |
199 | Offline utilities may wish to use this class directly. |
200 | |
201 | =cut |
202 | |
203 | 1; |
204 | |
205 | |
206 | C<lib/MyAppDB/Role.pm>: |
207 | |
208 | package MyAppDB::Role; |
209 | |
210 | use base qw/DBIx::Class/; |
211 | |
212 | # Load required DBIC stuff |
213 | __PACKAGE__->load_components(qw/PK::Auto Core/); |
214 | # Set the table name |
215 | __PACKAGE__->table('roles'); |
216 | # Set columns in table |
217 | __PACKAGE__->add_columns(qw/id role/); |
218 | # Set the primary key for the table |
219 | __PACKAGE__->set_primary_key('id'); |
220 | |
221 | # |
222 | # Set relationships: |
223 | # |
224 | |
225 | # has_many(): |
226 | # args: |
227 | # 1) Name of relationship, DBIC will create accessor with this name |
228 | # 2) Name of the model class referenced by this relationship |
229 | # 3) Column name in *foreign* table |
230 | __PACKAGE__->has_many(map_user_role => 'MyAppDB::UserRole', 'role_id'); |
231 | |
232 | |
233 | =head1 NAME |
234 | |
235 | MyAppDB::Role - A model object representing a class of access permissions to |
236 | the system. |
237 | |
238 | =head1 DESCRIPTION |
239 | |
240 | This is an object that represents a row in the 'roles' table of your |
241 | application database. It uses DBIx::Class (aka, DBIC) to do ORM. |
242 | |
243 | For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. |
244 | "Offline" utilities may wish to use this class directly. |
245 | |
246 | =cut |
247 | |
248 | 1; |
249 | |
250 | |
251 | C<lib/MyAppDB/UserRole.pm>: |
252 | |
253 | package MyAppDB::UserRole; |
254 | |
255 | use base qw/DBIx::Class/; |
256 | |
257 | # Load required DBIC stuff |
258 | __PACKAGE__->load_components(qw/PK::Auto Core/); |
259 | # Set the table name |
260 | __PACKAGE__->table('user_roles'); |
261 | # Set columns in table |
262 | __PACKAGE__->add_columns(qw/user_id role_id/); |
263 | # Set the primary key for the table |
264 | __PACKAGE__->set_primary_key(qw/user_id role_id/); |
265 | |
266 | # |
267 | # Set relationships: |
268 | # |
269 | |
270 | # belongs_to(): |
271 | # args: |
272 | # 1) Name of relationship, DBIC will create accessor with this name |
273 | # 2) Name of the model class referenced by this relationship |
274 | # 3) Column name in *this* table |
275 | __PACKAGE__->belongs_to(user => 'MyAppDB::User', 'user_id'); |
276 | |
277 | # belongs_to(): |
278 | # args: |
279 | # 1) Name of relationship, DBIC will create accessor with this name |
280 | # 2) Name of the model class referenced by this relationship |
281 | # 3) Column name in *this* table |
282 | __PACKAGE__->belongs_to(role => 'MyAppDB::Role', 'role_id'); |
283 | |
284 | |
285 | =head1 NAME |
286 | |
287 | MyAppDB::UserRole - A model object representing the JOIN between Users and Roles. |
288 | |
289 | =head1 DESCRIPTION |
290 | |
291 | This is an object that represents a row in the 'user_roles' table of your application |
292 | database. It uses DBIx::Class (aka, DBIC) to do ORM. |
293 | |
294 | You probably won't need to use this class directly -- it will be automatically |
295 | used by DBIC where joins are needed. |
296 | |
297 | For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. |
298 | Offline utilities may wish to use this class directly. |
299 | |
300 | =cut |
301 | |
302 | 1; |
303 | |
304 | The code for these three result source classes is obviously very familiar to the C<Book>, C<Author>, and C<BookAuthor> classes created in Part 2. |
305 | |
306 | |
307 | =head2 Sanity-Check Reload of Development Server |
308 | |
309 | We aren't ready to try out the authentication just yet; we only want to do a quick check to be sure our model loads correctly. Press C<Ctrl-C> to kill the previous server instance (if it's still running) and restart it: |
310 | |
311 | $ script/myapp_server.pl |
312 | |
313 | Look for the three new model objects in the startup debug output: |
314 | |
315 | ... |
316 | .-------------------------------------------------------------------+----------. |
317 | | Class | Type | |
318 | +-------------------------------------------------------------------+----------+ |
319 | | MyApp::Controller::Books | instance | |
320 | | MyApp::Controller::Root | instance | |
321 | | MyApp::Model::MyAppDB | instance | |
322 | | MyApp::Model::MyAppDB::Author | class | |
323 | | MyApp::Model::MyAppDB::Book | class | |
324 | | MyApp::Model::MyAppDB::BookAuthor | class | |
325 | | MyApp::Model::MyAppDB::Role | class | |
326 | | MyApp::Model::MyAppDB::User | class | |
327 | | MyApp::Model::MyAppDB::UserRole | class | |
328 | | MyApp::View::TT | instance | |
329 | '-------------------------------------------------------------------+----------' |
330 | ... |
331 | |
332 | Again, notice that your "result source" classes have been "re-loaded" by Catalyst under C<MyApp::Model>. |
333 | |
334 | |
335 | =head2 Include Authentication and Session Plugins |
336 | |
337 | Edit C<lib/MyApp.pm> and update it as follows (everything below C<DefaultEnd> is new): |
338 | |
339 | use Catalyst qw/ |
340 | -Debug |
341 | ConfigLoader |
342 | Static::Simple |
343 | |
344 | Dumper |
345 | StackTrace |
346 | DefaultEnd |
347 | |
348 | Authentication |
349 | Authentication::Store::DBIC |
350 | Authentication::Credential::Password |
351 | |
352 | Session |
353 | Session::Store::FastMmap |
354 | Session::State::Cookie |
355 | /; |
356 | |
64ccd8a8 |
357 | The three C<Authentication> plugins work together to support |
358 | Authentication while the C<Session> plugins are required to maintain |
359 | state across multiple HTTP requests. Note that there are several |
360 | options for L<Session::Store|Catalyst::Plugin::Session::Store> (although |
361 | L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap> |
362 | is generally a good choice if you are on Unix; try |
363 | L<Cache::FileCache|Catalyst::Plugin::Cache::FileCache> if you are on |
364 | Win32) -- consult L<Session::Store|Catalyst::Plugin::Session::Store> and |
365 | its subclasses for additional information. |
4d583dd8 |
366 | |
367 | |
368 | =head2 Configure Authentication |
369 | |
64ccd8a8 |
370 | Although C<__PACKAGE__-E<gt>config(name =E<gt> 'value');> is still |
371 | supported, newer Catalyst applications tend to place all configuration |
372 | information in C<myapp.yml> and automatically load this information into |
373 | C<MyApp-E<gt>config> using the |
374 | L<ConfigLoader|Catalyst::Plugin::ConfigLoader> plugin. |
4d583dd8 |
375 | |
376 | Edit the C<myapp.yml> YAML and update it to match: |
377 | |
378 | --- |
379 | name: MyApp |
380 | authentication: |
381 | dbic: |
382 | # Note this first definition would be the same as setting |
383 | # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User' |
384 | # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file). |
385 | # |
386 | # This is the model object created by Catalyst::Model::DBIC from your |
387 | # schema (you created 'MyAppDB::User' but as the Catalyst startup |
388 | # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User'). |
389 | # NOTE: Omit 'MyAppDB::Model' to avoid a component lookup issue in Catalyst 5.66 |
390 | user_class: MyAppDB::User |
391 | # This is the name of the field in your 'users' table that contains the user's name |
392 | user_field: username |
393 | # This is the name of the field in your 'users' table that contains the password |
394 | password_field: password |
395 | # Other options can go here for hashed passwords |
396 | |
397 | Inline comments in the code above explain how each field is being used. |
398 | |
64ccd8a8 |
399 | B<TIP>: Although YAML uses a very simple and easy-to-ready format, it |
400 | does require the use of a consistent level of indenting. Be sure you |
401 | line up everything on a given 'level' with the same number of indents. |
402 | Also, be sure not to use C<tab> characters (YAML does not support them |
403 | because they are handled inconsistently across editors). |
4d583dd8 |
404 | |
405 | |
406 | =head2 Add Login and Logout Controllers |
407 | |
408 | Use the Catalyst create script to create two stub controller files: |
409 | |
410 | $ script/myapp_create.pl controller Login |
411 | $ script/myapp_create.pl controller Logout |
412 | |
64ccd8a8 |
413 | B<NOTE>: You could easily use a single controller here. For example, |
414 | you could have a C<User> controller with both C<login> and C<logout> |
415 | actions. Remember, Catalyst is designed to be very flexible, and leaves |
416 | such matters up to you, the designer and programmer. |
4d583dd8 |
417 | |
418 | Then open C<lib/MyApp/Controller/Login.pm> and add: |
419 | |
420 | =head2 default |
421 | |
422 | Login logic |
423 | |
424 | =cut |
425 | |
426 | sub default : Private { |
427 | my ($self, $c) = @_; |
428 | |
429 | # Get the username and password from form |
430 | my $username = $c->request->params->{username} || ""; |
431 | my $password = $c->request->params->{password} || ""; |
432 | |
433 | # If the username and password values were found in form |
434 | if ($username && $password) { |
435 | # Attempt to log the user in |
436 | if ($c->login($username, $password)) { |
437 | # If successful, then let them use the application |
438 | $c->response->redirect($c->uri_for('/books/list')); |
439 | return; |
440 | } else { |
441 | # Set an error message |
442 | $c->stash->{error_msg} = "Bad username or password."; |
443 | } |
444 | } |
445 | |
446 | # If either of above don't work out, send to the login page |
447 | $c->stash->{template} = 'login.tt2'; |
448 | } |
449 | |
64ccd8a8 |
450 | This controller fetches the C<username> and C<password> values from the |
451 | login form and attempts to perform a login. If successful, it redirects |
452 | the user to the book list page. If the login fails, the user will stay |
453 | at the login page but receive an error message. If the C<username> and |
454 | C<password> values are not present in the form, the user will be taken |
455 | to the empty login form. |
4d583dd8 |
456 | |
457 | Next, create a corresponding method in C<lib/MyApp/Controller/Logout.pm>: |
458 | |
459 | =head2 default |
460 | |
461 | Logout logic |
462 | |
463 | =cut |
464 | |
465 | sub default : Private { |
466 | my ($self, $c) = @_; |
467 | |
468 | # Clear the user's state |
469 | $c->logout; |
470 | |
471 | # Send the user to the starting |
472 | $c->response->redirect($c->uri_for('/')); |
473 | } |
474 | |
475 | |
476 | =head2 Add a Login Form TT Template Page |
477 | |
478 | Create a login form by opening C<root/src/login.tt2> and inserting: |
479 | |
480 | [% META title = 'Login' %] |
481 | |
482 | <!-- Login form --> |
483 | <form method="post" action=" [% Catalyst.uri_for('/login') %] "> |
484 | <table> |
485 | <tr> |
486 | <td>Username:</td> |
487 | <td><input type="text" name="username" size="40" /></td> |
488 | </tr> |
489 | <tr> |
490 | <td>Password:</td> |
491 | <td><input type="password" name="password" size="40" /></td> |
492 | </tr> |
493 | <tr> |
494 | <td colspan="2"><input type="submit" name="submit" value="Submit" /></td> |
495 | </tr> |
496 | </table> |
497 | </form> |
498 | |
499 | |
500 | =head2 Add Valid User Check |
501 | |
64ccd8a8 |
502 | We need something that provides enforcement for the authentication |
503 | mechanism -- a I<global> mechanism that prevents users who have not |
504 | passed authentication from reaching any pages except the login page. |
505 | This is generally done via an C<auto> action/method (prior to Catalyst |
506 | v5.66, this sort of thing would go in C<MyApp.pm>, but starting in |
507 | v5.66, the preferred location is C<lib/MyApp/Controller/Root.pm>). |
4d583dd8 |
508 | |
509 | Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert the following method: |
510 | |
511 | =head2 auto |
512 | |
513 | Check if there is a user and, if not, forward to login page |
514 | |
515 | =cut |
516 | |
517 | # Note that 'auto' runs after 'begin' but before your actions and that |
518 | # 'auto' "chain" (all from application path to most specific class are run) |
519 | sub auto : Private { |
520 | my ($self, $c) = @_; |
521 | |
522 | # Allow unauthenticated users to reach the login page |
523 | if ($c->request->path =~ /login/) { |
524 | return 1; |
525 | } |
526 | |
527 | # If a user doesn't exist, force login |
528 | if (!$c->user_exists) { |
529 | # Dump a log message to the development server debug output |
530 | $c->log->debug('***Root::auto User not found, forwarding to /login'); |
531 | # Redirect the user to the login page |
532 | $c->response->redirect($c->uri_for('/login')); |
533 | # Return 0 to cancel 'post-auto' processing and prevent use of application |
534 | return 0; |
535 | } |
536 | |
537 | # User found, so return 1 to continue with processing after this 'auto' |
538 | return 1; |
539 | } |
540 | |
64ccd8a8 |
541 | B<Note:> Catalyst provides a number of different types of actions, such |
542 | as C<Local>, C<Regex>, and C<Private>. You should refer to |
543 | L<Catalyst::Manual::Intro|Catalyst::Manual::Intro> for a more detailed |
544 | explanation, but the following bullet points provide a quick |
545 | introduction: |
4d583dd8 |
546 | |
547 | =over 4 |
548 | |
549 | =item * |
550 | |
64ccd8a8 |
551 | The majority of application use C<Local> actions for items that respond |
552 | to user requests and C<Private> actions for those that do not directly |
553 | respond to user input. |
4d583dd8 |
554 | |
555 | =item * |
556 | |
64ccd8a8 |
557 | There are five types of C<Private> actions: C<begin>, C<end>, |
558 | C<default>, C<index>, and C<auto>. |
4d583dd8 |
559 | |
560 | =item * |
561 | |
64ccd8a8 |
562 | Unlike the other private C<Private> actions where only a single method |
563 | is called for each request, I<every> auto action along the chain of |
564 | namespaces will be called. |
4d583dd8 |
565 | |
566 | =back |
567 | |
64ccd8a8 |
568 | By placing the authentication enforcement code inside the C<auto> method |
569 | of C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be |
570 | called for I<every> request that is received by the entire application. |
4d583dd8 |
571 | |
572 | |
573 | =head2 Displaying Content Only to Authenticated Users |
574 | |
64ccd8a8 |
575 | Let's say you want to provide some information on the login page that |
576 | changes depending on whether the user has authenticated yet. To do |
577 | this, open C<root/src/login.tt2> in your editor and add the following |
578 | lines to the bottom of the file: |
4d583dd8 |
579 | |
580 | <p> |
581 | [% |
582 | # This code illustrates how certain parts of the TT |
583 | # template will only be shown to users who have logged in |
584 | %] |
585 | [% IF Catalyst.user %] |
586 | Please Note: You are already logged in as '[% Catalyst.user.username %]'. |
587 | You can <a href="[% Catalyst.uri_for('/logout') %]">logout</a> here. |
588 | [% ELSE %] |
589 | You need to log in to use this application. |
590 | [% END %] |
591 | [%# |
592 | Note that this whole block is a comment because the "#" appears |
593 | immediate after the "[%" (with no spaces in between). Although it |
594 | can be a handy way to temporarily "comment out" a whole block of |
595 | TT code, it's probably a little too subtle for use in "normal" |
596 | comments. |
597 | %] |
598 | |
64ccd8a8 |
599 | Although most of the code is comments, the middle few lines provide a |
600 | "you are already logged in" reminder if the user returns to the login |
601 | page after they have already authenticated. For users who have not yet |
602 | authenticated, a "You need to log in..." message is displayed (note the |
603 | use of an IF-THEN-ELSE construct in TT). |
4d583dd8 |
604 | |
605 | |
606 | =head2 Try Out Authentication |
607 | |
64ccd8a8 |
608 | Press C<Ctrl-C> to kill the previous server instance (if it's still |
609 | running) and restart it: |
4d583dd8 |
610 | |
611 | $ script/myapp_server.pl |
612 | |
64ccd8a8 |
613 | B<IMPORTANT NOTE>: If you happen to be using Internet Explorer, you may |
614 | need to use the command C<script/myapp_server.pl -k> to enable the |
615 | keepalive feature in the development server. Otherwise, the HTTP |
616 | redirect on successful login may not work correctly with IE (it seems to |
617 | work without -k if you are running the web browser and development |
618 | server on the same machine). If you are using browser a browser other |
619 | than IE, it should work either way. If you want to make keepalive the |
620 | default, you can edit C<script/myapp_server.pl> and change the |
621 | initialization value for C<$keepalive> to C<1>. (You will need to do |
622 | this every time you create a new Catalyst application or rebuild the |
623 | C<myapp_server.pl> script.) |
624 | |
625 | Now trying going to L<http://localhost:3000/books/list> and you should |
626 | be redirected to the login page, hitting Shift+Reload if necessary (the |
627 | "You are already logged in" message should I<not> appear -- if it does, |
628 | click the C<logout> button and try again). Make note of the |
629 | C<***Root::auto User not found...> debug message in the development |
630 | server output. Enter username C<test01> and password C<mypass>, and you |
631 | should be taken to the Book List page. |
4d583dd8 |
632 | |
633 | Open C< root/src/books/list.tt2> and add the following lines to the bottom: |
634 | |
635 | <p> |
636 | <a href="[% Catalyst.uri_for('/login') %]">Login</a> |
637 | <a href="[% Catalyst.uri_for('form_create') %]">Create</a> |
638 | </p> |
639 | |
64ccd8a8 |
640 | Reload your browser and you should now see a "Login" link at the bottom |
641 | of the page (as mentioned earlier, you can update template files without |
642 | reloading the development server). Click this link to return to the |
643 | login page. This time you I<should> see the "You are already logged in" |
644 | message. |
4d583dd8 |
645 | |
64ccd8a8 |
646 | Finally, click the C<You can logout here> link on the C</login> page. |
647 | You should stay at the login page, but the message should change to "You |
648 | need to log in to use this application." |
4d583dd8 |
649 | |
650 | |
651 | |
652 | =head1 USING PASSWORD HASHES |
653 | |
64ccd8a8 |
654 | In this section we increase the security of our system by converting |
655 | from cleartext passwords to SHA-1 password hashes. |
4d583dd8 |
656 | |
64ccd8a8 |
657 | B<Note:> This section is optional. You can skip it and the rest of the |
658 | tutorial will function normally. |
4d583dd8 |
659 | |
64ccd8a8 |
660 | Note that even with the techniques shown in this section, the browser |
661 | still transmits the passwords in cleartext to your application. We are |
662 | just avoiding the I<storage> of cleartext passwords in the database by |
663 | using a SHA-1 hash. If you are concerned about cleartext passwords |
664 | between the browser and your application, consider using SSL/TLS. |
4d583dd8 |
665 | |
666 | |
667 | =head2 Get a SHA-1 Hash for the Password |
668 | |
64ccd8a8 |
669 | Catalyst uses the C<Digest> module to support a variety of hashing |
670 | algorithms. Here we will use SHA-1 (SHA = Secure Hash Algorithm). |
671 | First, we should compute the SHA-1 hash for the "mypass" password we are |
672 | using. The following command-line Perl script provides a "quick and |
673 | dirty" way to do this: |
4d583dd8 |
674 | |
675 | $ perl -MDigest::SHA -e 'print Digest::SHA::sha1_hex("mypass"), "\n"' |
676 | e727d1464ae12436e899a726da5b2f11d8381b26 |
677 | $ |
678 | |
679 | |
680 | =head2 Switch to SHA-1 Password Hashes in the Database |
681 | |
64ccd8a8 |
682 | Next, we need to change the C<password> column of our C<users> table to |
683 | store this hash value vs. the existing cleartext password. Open |
684 | C<myapp03.sql> in your editor and enter: |
4d583dd8 |
685 | |
686 | -- |
687 | -- Convert passwords to SHA-1 hashes |
688 | -- |
689 | UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1; |
690 | UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2; |
691 | UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3; |
692 | |
693 | Then use the following command to update the SQLite database: |
694 | |
695 | $ sqlite3 myapp.db < myapp03.sql |
696 | |
64ccd8a8 |
697 | B<Note:> We are using SHA-1 hashes here, but many other hashing |
698 | algorithms are supported. See C<Digest> for more information. |
4d583dd8 |
699 | |
700 | |
64ccd8a8 |
701 | =head2 Enable SHA-1 Hash Passwords in |
702 | C<Catalyst::Plugin::Authentication::Store::DBIC> |
4d583dd8 |
703 | |
64ccd8a8 |
704 | Edit C<myapp.yml> and update it to match (the C<password_type> and |
705 | C<password_hash_type> are new, everything else is the same): |
4d583dd8 |
706 | |
707 | --- |
708 | name: MyApp |
709 | authentication: |
710 | dbic: |
711 | # Note this first definition would be the same as setting |
712 | # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User' |
713 | # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file). |
714 | # |
715 | # This is the model object created by Catalyst::Model::DBIC from your |
716 | # schema (you created 'MyAppDB::User' but as the Catalyst startup |
717 | # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User'). |
718 | # NOTE: Omit 'MyAppDB::Model' to avoid a component lookup issue in Catalyst 5.66 |
719 | user_class: MyAppDB::User |
720 | # This is the name of the field in your 'users' table that contains the user's name |
721 | user_field: username |
722 | # This is the name of the field in your 'users' table that contains the password |
723 | password_field: password |
724 | # Other options can go here for hashed passwords |
725 | # Enabled hashed passwords |
726 | password_type: hashed |
727 | # Use the SHA-1 hashing algorithm |
728 | password_hash_type: SHA-1 |
729 | |
730 | |
731 | =head2 Try Out the Hashed Passwords |
732 | |
64ccd8a8 |
733 | Press C<Ctrl-C> to kill the previous server instance (if it's still |
734 | running) and restart it: |
4d583dd8 |
735 | |
736 | $ script/myapp_server.pl |
737 | |
64ccd8a8 |
738 | You should now be able to go to L<http://localhost:3000/books/list> and |
739 | login as before. When done, click the "Logout" link on the login page |
740 | (or point your browser at L<http://localhost:3000/logout>). |
4d583dd8 |
741 | |
742 | =head1 AUTHOR |
743 | |
744 | Kennedy Clark, C<hkclark@gmail.com> |
745 | |
746 | Please report any errors, issues or suggestions to the author. |
747 | |
748 | Copyright 2006, Kennedy Clark. All rights reserved. |
749 | |
64ccd8a8 |
750 | This library is free software; you can redistribute it and/or modify it |
751 | under the same terms as Perl itself. |
4d583dd8 |
752 | |
753 | Version: .94 |
754 | |