Commit | Line | Data |
d442cc9f |
1 | =head1 NAME |
2 | |
3533daff |
3 | Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 5: Authentication |
d442cc9f |
4 | |
5 | |
6 | =head1 OVERVIEW |
7 | |
3533daff |
8 | This is B<Part 5 of 10> for the Catalyst tutorial. |
d442cc9f |
9 | |
10 | L<Tutorial Overview|Catalyst::Manual::Tutorial> |
11 | |
12 | =over 4 |
13 | |
14 | =item 1 |
15 | |
16 | L<Introduction|Catalyst::Manual::Tutorial::Intro> |
17 | |
18 | =item 2 |
19 | |
20 | L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics> |
21 | |
22 | =item 3 |
23 | |
3533daff |
24 | L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics> |
d442cc9f |
25 | |
26 | =item 4 |
27 | |
3533daff |
28 | L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD> |
d442cc9f |
29 | |
30 | =item 5 |
31 | |
3533daff |
32 | B<Authentication> |
d442cc9f |
33 | |
34 | =item 6 |
35 | |
3533daff |
36 | L<Authorization|Catalyst::Manual::Tutorial::Authorization> |
d442cc9f |
37 | |
38 | =item 7 |
39 | |
3533daff |
40 | L<Debugging|Catalyst::Manual::Tutorial::Debugging> |
d442cc9f |
41 | |
42 | =item 8 |
43 | |
3533daff |
44 | L<Testing|Catalyst::Manual::Tutorial::Testing> |
d442cc9f |
45 | |
46 | =item 9 |
47 | |
3533daff |
48 | L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD> |
d442cc9f |
49 | |
3533daff |
50 | =item 10 |
d442cc9f |
51 | |
3533daff |
52 | L<Appendices|Catalyst::Manual::Tutorial::Appendices> |
d442cc9f |
53 | |
3533daff |
54 | =back |
2d0526d1 |
55 | |
2d0526d1 |
56 | |
d442cc9f |
57 | =head1 DESCRIPTION |
58 | |
905a3a26 |
59 | Now that we finally have a simple yet functional application, we can |
60 | focus on providing authentication (with authorization coming next in |
9ad715b3 |
61 | Part 6). |
d442cc9f |
62 | |
63 | This part of the tutorial is divided into two main sections: 1) basic, |
64 | cleartext authentication and 2) hash-based authentication. |
65 | |
66 | You can checkout the source code for this example from the catalyst |
67 | subversion repository as per the instructions in |
1390ef0e |
68 | L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>. |
d442cc9f |
69 | |
70 | =head1 BASIC AUTHENTICATION |
71 | |
72 | This section explores how to add authentication logic to a Catalyst |
73 | application. |
74 | |
75 | |
76 | =head2 Add Users and Roles to the Database |
77 | |
78 | First, we add both user and role information to the database (we will |
79 | add the role information here although it will not be used until the |
9ad715b3 |
80 | authorization section, Part 6). Create a new SQL script file by opening |
d442cc9f |
81 | C<myapp02.sql> in your editor and insert: |
82 | |
83 | -- |
84 | -- Add users and roles tables, along with a many-to-many join table |
85 | -- |
86 | CREATE TABLE users ( |
87 | id INTEGER PRIMARY KEY, |
88 | username TEXT, |
89 | password TEXT, |
90 | email_address TEXT, |
91 | first_name TEXT, |
92 | last_name TEXT, |
93 | active INTEGER |
94 | ); |
95 | CREATE TABLE roles ( |
96 | id INTEGER PRIMARY KEY, |
97 | role TEXT |
98 | ); |
99 | CREATE TABLE user_roles ( |
100 | user_id INTEGER, |
101 | role_id INTEGER, |
102 | PRIMARY KEY (user_id, role_id) |
103 | ); |
104 | -- |
105 | -- Load up some initial test data |
106 | -- |
107 | INSERT INTO users VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe', 'Blow', 1); |
108 | INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe', 1); |
109 | INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', 'No', 'Go', 0); |
110 | INSERT INTO roles VALUES (1, 'user'); |
111 | INSERT INTO roles VALUES (2, 'admin'); |
112 | INSERT INTO user_roles VALUES (1, 1); |
113 | INSERT INTO user_roles VALUES (1, 2); |
114 | INSERT INTO user_roles VALUES (2, 1); |
115 | INSERT INTO user_roles VALUES (3, 1); |
116 | |
117 | Then load this into the C<myapp.db> database with the following command: |
118 | |
119 | $ sqlite3 myapp.db < myapp02.sql |
120 | |
121 | |
122 | =head2 Add User and Role Information to DBIC Schema |
123 | |
3533daff |
124 | Although we could manually edit the DBIC schema information to include |
125 | the new tables added in the previous step, let's use the C<create=static> |
126 | option on the DBIC model helper to do most of the work for us: |
d442cc9f |
127 | |
d0496197 |
128 | $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema create=static dbi:SQLite:myapp.db |
1390ef0e |
129 | exists "/root/dev/MyApp/script/../lib/MyApp/Model" |
130 | exists "/root/dev/MyApp/script/../t" |
131 | Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ... |
132 | Schema dump completed. |
133 | exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm" |
134 | $ |
d0496197 |
135 | $ ls lib/MyApp/Schema |
3533daff |
136 | Authors.pm BookAuthors.pm Books.pm Roles.pm UserRoles.pm Users.pm |
d442cc9f |
137 | |
905a3a26 |
138 | Notice how the helper has added three new table-specific result source |
139 | files to the C<lib/MyApp/Schema/MyApp> directory. And, more |
140 | importantly, even if there were changes to the existing result source |
141 | files, those changes would have only been written above the C<# DO NOT |
142 | MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-editted |
3533daff |
143 | enhancements would have been preserved. |
d442cc9f |
144 | |
905a3a26 |
145 | Speaking of "hand-editted enhancements," we should now add |
146 | relationship information to the three new result source files. Edit |
147 | each of these files and add the following information between the C<# |
3533daff |
148 | DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>: |
d442cc9f |
149 | |
d0496197 |
150 | C<lib/MyApp/Schema/Users.pm>: |
d442cc9f |
151 | |
d442cc9f |
152 | # |
153 | # Set relationships: |
154 | # |
1390ef0e |
155 | |
d442cc9f |
156 | # has_many(): |
157 | # args: |
158 | # 1) Name of relationship, DBIC will create accessor with this name |
159 | # 2) Name of the model class referenced by this relationship |
160 | # 3) Column name in *foreign* table |
d0496197 |
161 | __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::UserRoles', 'user_id'); |
1390ef0e |
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(). |
169 | __PACKAGE__->many_to_many(roles => 'map_user_role', 'role'); |
d442cc9f |
170 | |
171 | |
d0496197 |
172 | C<lib/MyApp/Schema/Roles.pm>: |
d442cc9f |
173 | |
d442cc9f |
174 | # |
175 | # Set relationships: |
176 | # |
1390ef0e |
177 | |
d442cc9f |
178 | # has_many(): |
179 | # args: |
180 | # 1) Name of relationship, DBIC will create accessor with this name |
181 | # 2) Name of the model class referenced by this relationship |
182 | # 3) Column name in *foreign* table |
d0496197 |
183 | __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::UserRoles', 'role_id'); |
d442cc9f |
184 | |
185 | |
d0496197 |
186 | C<lib/MyApp/Schema/UserRoles.pm>: |
d442cc9f |
187 | |
d442cc9f |
188 | # |
189 | # Set relationships: |
190 | # |
1390ef0e |
191 | |
d442cc9f |
192 | # belongs_to(): |
193 | # args: |
194 | # 1) Name of relationship, DBIC will create accessor with this name |
195 | # 2) Name of the model class referenced by this relationship |
196 | # 3) Column name in *this* table |
d0496197 |
197 | __PACKAGE__->belongs_to(user => 'MyApp::Schema::Users', 'user_id'); |
1390ef0e |
198 | |
d442cc9f |
199 | # belongs_to(): |
200 | # args: |
201 | # 1) Name of relationship, DBIC will create accessor with this name |
202 | # 2) Name of the model class referenced by this relationship |
203 | # 3) Column name in *this* table |
d0496197 |
204 | __PACKAGE__->belongs_to(role => 'MyApp::Schema::Roles', 'role_id'); |
d442cc9f |
205 | |
3533daff |
206 | |
905a3a26 |
207 | The code for these three sets of updates is obviously very similar to |
208 | the edits we made to the C<Books>, C<Authors>, and C<BookAuthors> |
3533daff |
209 | classes created in Part 3. |
210 | |
905a3a26 |
211 | Note that we do not need to make any change to the |
212 | C<lib/MyApp/Schema.pm> schema file. It simply tells DBIC to |
79a529cc |
213 | load all of the result class files it finds in below the |
905a3a26 |
214 | C<lib/MyApp/Schema> directory, so it will automatically pick |
3533daff |
215 | up our new table information. |
d442cc9f |
216 | |
217 | |
218 | =head2 Sanity-Check Reload of Development Server |
219 | |
905a3a26 |
220 | We aren't ready to try out the authentication just yet; we only want |
221 | to do a quick check to be sure our model loads correctly. Press |
222 | C<Ctrl-C> to kill the previous server instance (if it's still running) |
3533daff |
223 | and restart it: |
d442cc9f |
224 | |
225 | $ script/myapp_server.pl |
226 | |
227 | Look for the three new model objects in the startup debug output: |
228 | |
229 | ... |
230 | .-------------------------------------------------------------------+----------. |
231 | | Class | Type | |
232 | +-------------------------------------------------------------------+----------+ |
233 | | MyApp::Controller::Books | instance | |
234 | | MyApp::Controller::Root | instance | |
d0496197 |
235 | | MyApp::Model::DB | instance | |
236 | | MyApp::Model::DB::Author | class | |
237 | | MyApp::Model::DB::Books | class | |
238 | | MyApp::Model::DB::BookAuthors | class | |
239 | | MyApp::Model::DB::Roles | class | |
240 | | MyApp::Model::DB::Users | class | |
241 | | MyApp::Model::DB::UserRoles | class | |
d442cc9f |
242 | | MyApp::View::TT | instance | |
243 | '-------------------------------------------------------------------+----------' |
244 | ... |
245 | |
79a529cc |
246 | Again, notice that your "result class" classes have been "re-loaded" |
3533daff |
247 | by Catalyst under C<MyApp::Model>. |
d442cc9f |
248 | |
249 | |
250 | =head2 Include Authentication and Session Plugins |
251 | |
905a3a26 |
252 | Edit C<lib/MyApp.pm> and update it as follows (everything below |
3533daff |
253 | C<StackTrace> is new): |
d442cc9f |
254 | |
1390ef0e |
255 | __PACKAGE__->setup(qw/ |
d442cc9f |
256 | -Debug |
257 | ConfigLoader |
258 | Static::Simple |
1390ef0e |
259 | |
d442cc9f |
260 | StackTrace |
1390ef0e |
261 | |
d442cc9f |
262 | Authentication |
1390ef0e |
263 | |
d442cc9f |
264 | Session |
265 | Session::Store::FastMmap |
266 | Session::State::Cookie |
1390ef0e |
267 | /); |
d442cc9f |
268 | |
905a3a26 |
269 | The C<Authentication> plugin supports Authentication while the |
270 | C<Session> plugins are required to maintain state across multiple HTTP |
271 | requests. |
6d0971ad |
272 | |
905a3a26 |
273 | Note that the only required Authentication class is the main one. This |
274 | is a change that occurred in version 0.09999_01 of the |
275 | C<Authentication> plugin. You B<do not need> to specify a particular |
276 | Authentication::Store or Authentication::Credential plugin. Instead, |
277 | indicate the Store and Credential you want to use in your application |
6d0971ad |
278 | configuration (see below). |
279 | |
905a3a26 |
280 | Note that there are several options for |
281 | L<Session::Store|Catalyst::Plugin::Session::Store> |
282 | (L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap> |
283 | is generally a good choice if you are on Unix; try |
284 | L<Session::Store::File|Catalyst::Plugin::Session::Store::File> if you |
285 | are on Win32) -- consult |
286 | L<Session::Store|Catalyst::Plugin::Session::Store> and its subclasses |
3533daff |
287 | for additional information and options (for example to use a database- |
288 | backed session store). |
d442cc9f |
289 | |
290 | |
291 | =head2 Configure Authentication |
292 | |
905a3a26 |
293 | Although C<__PACKAGE__-E<gt>config(name =E<gt> 'value');> is still |
294 | supported, newer Catalyst applications tend to place all configuration |
295 | information in C<myapp.conf> and automatically load this information |
296 | into C<MyApp-E<gt>config> using the |
297 | L<ConfigLoader|Catalyst::Plugin::ConfigLoader> plugin. |
15e1d0b2 |
298 | |
905a3a26 |
299 | First, as noted in Part 3 of the tutorial, Catalyst has recently |
300 | switched from a default config file format of YAML to |
79a529cc |
301 | C<Config::General> (an apache-like format). In case you are using a |
302 | version of Catalyst earlier than v5.7014, delete the C<myapp.yml>, or |
303 | convert it to .conf format using the TIP in |
304 | L<Catalyst::Manual::MoreCatalystBasics>; then simply follow the |
305 | directions below to create a new C<myapp.conf> file. |
15e1d0b2 |
306 | |
905a3a26 |
307 | Here, we need to load several parameters that tell |
308 | L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication> |
309 | where to locate information in your database. To do this, edit the |
c010ae0d |
310 | C<myapp.conf> file and update it to match: |
311 | |
1390ef0e |
312 | # rename this file to MyApp.yml and put a : in front of "name" if |
313 | # you want to use yaml like in old versions of Catalyst |
c010ae0d |
314 | name MyApp |
315 | <authentication> |
316 | default_realm dbic |
317 | <realms> |
318 | <dbic> |
319 | <credential> |
905a3a26 |
320 | # Note: this first definition would be the same as setting |
3533daff |
321 | # __PACKAGE__->config->{authentication}->{realms}->{dbic} |
905a3a26 |
322 | # ->{credential} = 'Password' in lib/MyApp.pm |
3533daff |
323 | # |
324 | # Specify that we are going to do password-based auth |
c010ae0d |
325 | class Password |
3533daff |
326 | # This is the name of the field in the users table with the |
327 | # password stored in it |
c010ae0d |
328 | password_field password |
905a3a26 |
329 | # We are using an unencrypted password for now |
c010ae0d |
330 | password_type clear |
331 | </credential> |
332 | <store> |
3533daff |
333 | # Use DBIC to retrieve username, password & role information |
c010ae0d |
334 | class DBIx::Class |
905a3a26 |
335 | # This is the model object created by Catalyst::Model::DBIC |
d0496197 |
336 | # from your schema (you created 'MyApp::Schema::User' but as |
905a3a26 |
337 | # the Catalyst startup debug messages show, it was loaded as |
d0496197 |
338 | # 'MyApp::Model::DB::Users'). |
905a3a26 |
339 | # NOTE: Omit 'MyApp::Model' here just as you would when using |
d0496197 |
340 | # '$c->model("DB::Users)' |
341 | user_class DB::Users |
c010ae0d |
342 | </store> |
343 | </dbic> |
d0496197 |
344 | </realms> |
345 | </authentication> |
d442cc9f |
346 | |
347 | Inline comments in the code above explain how each field is being used. |
348 | |
c010ae0d |
349 | Note that you can use many other config file formats with catalyst. |
350 | See L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> |
351 | for details. |
d442cc9f |
352 | |
1390ef0e |
353 | |
d442cc9f |
354 | =head2 Add Login and Logout Controllers |
355 | |
356 | Use the Catalyst create script to create two stub controller files: |
357 | |
358 | $ script/myapp_create.pl controller Login |
359 | $ script/myapp_create.pl controller Logout |
360 | |
d0496197 |
361 | B<NOTE:> You could easily use a single controller here. For example, |
d442cc9f |
362 | you could have a C<User> controller with both C<login> and C<logout> |
363 | actions. Remember, Catalyst is designed to be very flexible, and leaves |
364 | such matters up to you, the designer and programmer. |
365 | |
905a3a26 |
366 | Then open C<lib/MyApp/Controller/Login.pm>, locate the C<sub index |
367 | :Path :Args(0)> method (or C<sub index : Private> if you are using an |
368 | older version of Catalyst) that was automatically inserted by the |
369 | helpers when we created the Login controller above, and delete this |
ae492862 |
370 | line: |
d442cc9f |
371 | |
372 | $c->response->body('Matched MyApp::Controller::Login in Login.'); |
373 | |
374 | Then update it to match: |
375 | |
376 | =head2 index |
1390ef0e |
377 | |
d442cc9f |
378 | Login logic |
1390ef0e |
379 | |
d442cc9f |
380 | =cut |
1390ef0e |
381 | |
ae492862 |
382 | sub index :Path :Args(0) { |
d442cc9f |
383 | my ($self, $c) = @_; |
1390ef0e |
384 | |
d442cc9f |
385 | # Get the username and password from form |
386 | my $username = $c->request->params->{username} || ""; |
387 | my $password = $c->request->params->{password} || ""; |
1390ef0e |
388 | |
d442cc9f |
389 | # If the username and password values were found in form |
390 | if ($username && $password) { |
391 | # Attempt to log the user in |
905a3a26 |
392 | if ($c->authenticate({ username => $username, |
5fefca35 |
393 | password => $password } )) { |
d442cc9f |
394 | # If successful, then let them use the application |
395 | $c->response->redirect($c->uri_for('/books/list')); |
396 | return; |
397 | } else { |
398 | # Set an error message |
399 | $c->stash->{error_msg} = "Bad username or password."; |
400 | } |
401 | } |
1390ef0e |
402 | |
d442cc9f |
403 | # If either of above don't work out, send to the login page |
404 | $c->stash->{template} = 'login.tt2'; |
405 | } |
406 | |
407 | This controller fetches the C<username> and C<password> values from the |
905a3a26 |
408 | login form and attempts to authenticate the user. If successful, it |
409 | redirects the user to the book list page. If the login fails, the user |
410 | will stay at the login page and receive an error message. If the |
411 | C<username> and C<password> values are not present in the form, the |
f632e28b |
412 | user will be taken to the empty login form. |
d442cc9f |
413 | |
1390ef0e |
414 | Note that we could have used something like C<sub default :Path>, |
415 | however, it is generally recommended (partly for historical reasons, |
416 | and partly for code clarity) only to use C<default> in |
417 | C<MyApp::Controller::Root>, and then mainly to generate the 404 not |
85d49fb6 |
418 | found page for the application. |
ae492862 |
419 | |
905a3a26 |
420 | Instead, we are using C<sub base :Path :Args(0) {...}> here to |
421 | specifically match the URL C</login>. C<Path> actions (aka, "literal |
422 | actions") create URI matches relative to the namespace of the |
423 | controller where they are defined. Although C<Path> supports |
424 | arguments that allow relative and absolute paths to be defined, here |
425 | we use an empty C<Path> definition to match on just the name of the |
426 | controller itself. The method name, C<index>, is arbitrary. We make |
ae492862 |
427 | the match even more specific with the C<:Args(0)> action modifier -- |
905a3a26 |
428 | this forces the match on I<only> C</login>, not |
d442cc9f |
429 | C</login/somethingelse>. |
430 | |
905a3a26 |
431 | Next, update the corresponding method in |
3533daff |
432 | C<lib/MyApp/Controller/Logout.pm> to match: |
d442cc9f |
433 | |
434 | =head2 index |
1390ef0e |
435 | |
d442cc9f |
436 | Logout logic |
1390ef0e |
437 | |
d442cc9f |
438 | =cut |
1390ef0e |
439 | |
ae492862 |
440 | sub index :Path :Args(0) { |
d442cc9f |
441 | my ($self, $c) = @_; |
1390ef0e |
442 | |
d442cc9f |
443 | # Clear the user's state |
444 | $c->logout; |
1390ef0e |
445 | |
d442cc9f |
446 | # Send the user to the starting point |
447 | $c->response->redirect($c->uri_for('/')); |
448 | } |
449 | |
905a3a26 |
450 | As with the login controller, be sure to delete the |
14e5ed66 |
451 | C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Logout in Logout.');> |
d442cc9f |
452 | line of the C<sub index>. |
453 | |
454 | |
455 | =head2 Add a Login Form TT Template Page |
456 | |
457 | Create a login form by opening C<root/src/login.tt2> and inserting: |
458 | |
459 | [% META title = 'Login' %] |
1390ef0e |
460 | |
d442cc9f |
461 | <!-- Login form --> |
8a7c5151 |
462 | <form method="post" action="[% c.uri_for('/login') %]"> |
d442cc9f |
463 | <table> |
464 | <tr> |
465 | <td>Username:</td> |
466 | <td><input type="text" name="username" size="40" /></td> |
467 | </tr> |
468 | <tr> |
469 | <td>Password:</td> |
470 | <td><input type="password" name="password" size="40" /></td> |
471 | </tr> |
472 | <tr> |
473 | <td colspan="2"><input type="submit" name="submit" value="Submit" /></td> |
474 | </tr> |
475 | </table> |
476 | </form> |
477 | |
478 | |
479 | =head2 Add Valid User Check |
480 | |
481 | We need something that provides enforcement for the authentication |
482 | mechanism -- a I<global> mechanism that prevents users who have not |
483 | passed authentication from reaching any pages except the login page. |
484 | This is generally done via an C<auto> action/method (prior to Catalyst |
485 | v5.66, this sort of thing would go in C<MyApp.pm>, but starting in |
486 | v5.66, the preferred location is C<lib/MyApp/Controller/Root.pm>). |
487 | |
488 | Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert |
489 | the following method: |
490 | |
491 | =head2 auto |
1390ef0e |
492 | |
d442cc9f |
493 | Check if there is a user and, if not, forward to login page |
1390ef0e |
494 | |
d442cc9f |
495 | =cut |
1390ef0e |
496 | |
d442cc9f |
497 | # Note that 'auto' runs after 'begin' but before your actions and that |
905a3a26 |
498 | # 'auto's "chain" (all from application path to most specific class are run) |
d442cc9f |
499 | # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info. |
500 | sub auto : Private { |
501 | my ($self, $c) = @_; |
1390ef0e |
502 | |
d442cc9f |
503 | # Allow unauthenticated users to reach the login page. This |
504 | # allows anauthenticated users to reach any action in the Login |
505 | # controller. To lock it down to a single action, we could use: |
506 | # if ($c->action eq $c->controller('Login')->action_for('index')) |
905a3a26 |
507 | # to only allow unauthenticated access to the 'index' action we |
d442cc9f |
508 | # added above. |
509 | if ($c->controller eq $c->controller('Login')) { |
510 | return 1; |
511 | } |
1390ef0e |
512 | |
d442cc9f |
513 | # If a user doesn't exist, force login |
514 | if (!$c->user_exists) { |
515 | # Dump a log message to the development server debug output |
516 | $c->log->debug('***Root::auto User not found, forwarding to /login'); |
517 | # Redirect the user to the login page |
518 | $c->response->redirect($c->uri_for('/login')); |
519 | # Return 0 to cancel 'post-auto' processing and prevent use of application |
520 | return 0; |
521 | } |
1390ef0e |
522 | |
d442cc9f |
523 | # User found, so return 1 to continue with processing after this 'auto' |
524 | return 1; |
525 | } |
526 | |
3533daff |
527 | |
905a3a26 |
528 | B<Note:> Catalyst provides a number of different types of actions, |
529 | such as C<Local>, C<Regex>, C<Private> and the new C<Path>. You |
530 | should refer to L<Catalyst::Manual::Intro|Catalyst::Manual::Intro> for |
531 | a more detailed explanation, but the following bullet points provide a |
3533daff |
532 | quick introduction: |
d442cc9f |
533 | |
534 | =over 4 |
535 | |
536 | =item * |
537 | |
905a3a26 |
538 | The majority of application have traditionally used C<Local> actions |
539 | for items that respond to user requests and C<Private> actions for |
3533daff |
540 | those that do not directly respond to user input. |
d442cc9f |
541 | |
542 | =item * |
543 | |
905a3a26 |
544 | Newer Catalyst applications tend to use C<Path> actions and the |
3533daff |
545 | C<Args> attribute because of their power and flexibility. You can |
546 | specify the path to match relative to the namespace of the current |
547 | module as an argument to C<Path>. For example C<Path('list')> in |
905a3a26 |
548 | C<lib/MyApp/Controller/Books.pm> would match on the URL |
549 | C<http://localhost:3000/books/list> but C<Path('/list')> would |
3533daff |
550 | match on C<http://localhost:3000/list>. |
551 | |
552 | =item * |
553 | |
905a3a26 |
554 | Automatic "chaining" of actions by the dispatcher is a powerful |
555 | feature that allows multiple methods to handle a single URL. See |
3533daff |
556 | L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained> |
557 | for more information on chained actions. |
558 | |
559 | =item * |
560 | |
561 | There are five types of build-in C<Private> actions: C<begin>, C<end>, |
d442cc9f |
562 | C<default>, C<index>, and C<auto>. |
563 | |
564 | =item * |
565 | |
566 | With C<begin>, C<end>, C<default>, C<index> private actions, only the |
567 | most specific action of each type will be called. For example, if you |
905a3a26 |
568 | define a C<begin> action in your controller it will I<override> a |
d442cc9f |
569 | C<begin> action in your application/root controller -- I<only> the |
570 | action in your controller will be called. |
571 | |
572 | =item * |
573 | |
905a3a26 |
574 | Unlike the other actions where only a single method is called for each |
575 | request, I<every> auto action along the chain of namespaces will be |
d442cc9f |
576 | called. Each C<auto> action will be called I<from the application/root |
577 | controller down through the most specific class>. |
578 | |
579 | =back |
580 | |
581 | By placing the authentication enforcement code inside the C<auto> method |
582 | of C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be |
583 | called for I<every> request that is received by the entire application. |
584 | |
585 | |
586 | =head2 Displaying Content Only to Authenticated Users |
587 | |
588 | Let's say you want to provide some information on the login page that |
589 | changes depending on whether the user has authenticated yet. To do |
590 | this, open C<root/src/login.tt2> in your editor and add the following |
591 | lines to the bottom of the file: |
592 | |
593 | <p> |
594 | [% |
905a3a26 |
595 | # This code illustrates how certain parts of the TT |
d442cc9f |
596 | # template will only be shown to users who have logged in |
597 | %] |
8a7c5151 |
598 | [% IF c.user_exists %] |
599 | Please Note: You are already logged in as '[% c.user.username %]'. |
600 | You can <a href="[% c.uri_for('/logout') %]">logout</a> here. |
d442cc9f |
601 | [% ELSE %] |
602 | You need to log in to use this application. |
603 | [% END %] |
604 | [%# |
605 | Note that this whole block is a comment because the "#" appears |
905a3a26 |
606 | immediate after the "[%" (with no spaces in between). Although it |
607 | can be a handy way to temporarily "comment out" a whole block of |
608 | TT code, it's probably a little too subtle for use in "normal" |
d442cc9f |
609 | comments. |
610 | %] |
3533daff |
611 | </p> |
d442cc9f |
612 | |
613 | Although most of the code is comments, the middle few lines provide a |
614 | "you are already logged in" reminder if the user returns to the login |
615 | page after they have already authenticated. For users who have not yet |
616 | authenticated, a "You need to log in..." message is displayed (note the |
617 | use of an IF-THEN-ELSE construct in TT). |
618 | |
619 | |
620 | =head2 Try Out Authentication |
621 | |
622 | Press C<Ctrl-C> to kill the previous server instance (if it's still |
623 | running) and restart it: |
624 | |
625 | $ script/myapp_server.pl |
626 | |
1390ef0e |
627 | B<IMPORTANT NOTE:> If you are having issues with authentication on |
628 | Internet Explorer, be sure to check the system clocks on both your |
629 | server and client machines. Internet Explorer is very picky about |
630 | timestamps for cookies. Note that you can quickly sync an Ubuntu |
25ed8f40 |
631 | system with the following command: |
632 | |
633 | sudo ntpdate ntp.ubuntu.com |
d442cc9f |
634 | |
1390ef0e |
635 | Or possibly try C<sudo ntpdate -u ntp.ubuntu.com> (to us an |
636 | unpriviledged port) or C<sudo ntpdate pool.ntp.org> (to try a |
637 | different server in case the Ubuntu NTP server is down). |
638 | |
639 | Now trying going to L<http://localhost:3000/books/list> and you should |
640 | be redirected to the login page, hitting Shift+Reload or Ctrl+Reload |
641 | if necessary (the "You are already logged in" message should I<not> |
642 | appear -- if it does, click the C<logout> button and try again). Note |
643 | the C<***Root::auto User not found...> debug message in the |
644 | development server output. Enter username C<test01> and password |
645 | C<mypass>, and you should be taken to the Book List page. |
d442cc9f |
646 | |
647 | Open C<root/src/books/list.tt2> and add the following lines to the |
3533daff |
648 | bottom (below the closing </table> tag): |
d442cc9f |
649 | |
650 | <p> |
8a7c5151 |
651 | <a href="[% c.uri_for('/login') %]">Login</a> |
652 | <a href="[% c.uri_for('form_create') %]">Create</a> |
d442cc9f |
653 | </p> |
654 | |
905a3a26 |
655 | Reload your browser and you should now see a "Login" and "Create" links |
656 | at the bottom of the page (as mentioned earlier, you can update template |
657 | files without reloading the development server). Click the first link |
658 | to return to the login page. This time you I<should> see the "You are |
d442cc9f |
659 | already logged in" message. |
660 | |
661 | Finally, click the C<You can logout here> link on the C</login> page. |
662 | You should stay at the login page, but the message should change to "You |
663 | need to log in to use this application." |
664 | |
665 | |
666 | =head1 USING PASSWORD HASHES |
667 | |
668 | In this section we increase the security of our system by converting |
669 | from cleartext passwords to SHA-1 password hashes. |
670 | |
671 | B<Note:> This section is optional. You can skip it and the rest of the |
672 | tutorial will function normally. |
673 | |
674 | Note that even with the techniques shown in this section, the browser |
675 | still transmits the passwords in cleartext to your application. We are |
676 | just avoiding the I<storage> of cleartext passwords in the database by |
677 | using a SHA-1 hash. If you are concerned about cleartext passwords |
678 | between the browser and your application, consider using SSL/TLS, made |
679 | easy with the Catalyst plugin Catalyst::Plugin:RequireSSL. |
680 | |
681 | |
682 | =head2 Get a SHA-1 Hash for the Password |
683 | |
684 | Catalyst uses the C<Digest> module to support a variety of hashing |
685 | algorithms. Here we will use SHA-1 (SHA = Secure Hash Algorithm). |
686 | First, we should compute the SHA-1 hash for the "mypass" password we are |
687 | using. The following command-line Perl script provides a "quick and |
688 | dirty" way to do this: |
689 | |
690 | $ perl -MDigest::SHA -e 'print Digest::SHA::sha1_hex("mypass"), "\n"' |
691 | e727d1464ae12436e899a726da5b2f11d8381b26 |
692 | $ |
693 | |
905a3a26 |
694 | B<Note:> If you are following along in Ubuntu, you will need to install |
d0496197 |
695 | C<Digest::SHA> with the following command to run the example code above: |
696 | |
905a3a26 |
697 | sudo aptitude install libdigest-sha-perl |
d0496197 |
698 | |
d442cc9f |
699 | B<Note:> You should probably modify this code for production use to |
700 | not read the password from the command line. By having the script |
701 | prompt for the cleartext password, it avoids having the password linger |
702 | in forms such as your C<.bash_history> files (assuming you are using |
703 | BASH as your shell). An example of such a script can be found in |
704 | Appendix 3. |
705 | |
706 | |
707 | =head2 Switch to SHA-1 Password Hashes in the Database |
708 | |
709 | Next, we need to change the C<password> column of our C<users> table to |
710 | store this hash value vs. the existing cleartext password. Open |
711 | C<myapp03.sql> in your editor and enter: |
712 | |
713 | -- |
714 | -- Convert passwords to SHA-1 hashes |
715 | -- |
716 | UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1; |
717 | UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2; |
718 | UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3; |
719 | |
720 | Then use the following command to update the SQLite database: |
721 | |
722 | $ sqlite3 myapp.db < myapp03.sql |
723 | |
1390ef0e |
724 | B<Note:> We are using SHA-1 hashes here, but many other hashing |
d442cc9f |
725 | algorithms are supported. See C<Digest> for more information. |
726 | |
727 | |
728 | =head2 Enable SHA-1 Hash Passwords in |
729 | C<Catalyst::Plugin::Authentication::Store::DBIC> |
730 | |
45d511e0 |
731 | Edit C<myapp.conf> and update it to match (the C<password_type> and |
d442cc9f |
732 | C<password_hash_type> are new, everything else is the same): |
733 | |
1390ef0e |
734 | # rename this file to MyApp.yml and put a : in front of "name" if |
735 | # you want to use yaml like in old versions of Catalyst |
c010ae0d |
736 | name MyApp |
737 | <authentication> |
738 | default_realm dbic |
739 | <realms> |
740 | <dbic> |
741 | <credential> |
3533daff |
742 | # Note this first definition would be the same as setting |
743 | # __PACKAGE__->config->{authentication}->{realms}->{dbic} |
905a3a26 |
744 | # ->{credential} = 'Password' in lib/MyApp.pm |
3533daff |
745 | # |
746 | # Specify that we are going to do password-based auth |
c010ae0d |
747 | class Password |
3533daff |
748 | # This is the name of the field in the users table with the |
749 | # password stored in it |
c010ae0d |
750 | password_field password |
3533daff |
751 | # Switch to more secure hashed passwords |
c010ae0d |
752 | password_type hashed |
3533daff |
753 | # Use the SHA-1 hashing algorithm |
c010ae0d |
754 | password_hash_type SHA-1 |
d0496197 |
755 | </credential> |
c010ae0d |
756 | <store> |
3533daff |
757 | # Use DBIC to retrieve username, password & role information |
c010ae0d |
758 | class DBIx::Class |
905a3a26 |
759 | # This is the model object created by Catalyst::Model::DBIC |
d0496197 |
760 | # from your schema (you created 'MyApp::Schema::User' but as |
905a3a26 |
761 | # the Catalyst startup debug messages show, it was loaded as |
d0496197 |
762 | # 'MyApp::Model::DB::Users'). |
905a3a26 |
763 | # NOTE: Omit 'MyApp::Model' here just as you would when using |
d0496197 |
764 | # '$c->model("DB::Users)' |
765 | user_class DB::Users |
d0496197 |
766 | </store> |
767 | </dbic> |
768 | </realms> |
769 | </authentication> |
d442cc9f |
770 | |
1390ef0e |
771 | |
d442cc9f |
772 | =head2 Try Out the Hashed Passwords |
773 | |
774 | Press C<Ctrl-C> to kill the previous server instance (if it's still |
775 | running) and restart it: |
776 | |
777 | $ script/myapp_server.pl |
778 | |
779 | You should now be able to go to L<http://localhost:3000/books/list> and |
780 | login as before. When done, click the "Logout" link on the login page |
781 | (or point your browser at L<http://localhost:3000/logout>). |
782 | |
d442cc9f |
783 | |
784 | =head1 USING THE SESSION FOR FLASH |
785 | |
786 | As discussed in Part 3 of the tutorial, C<flash> allows you to set |
905a3a26 |
787 | variables in a way that is very similar to C<stash>, but it will |
d442cc9f |
788 | remain set across multiple requests. Once the value is read, it |
789 | is cleared (unless reset). Although C<flash> has nothing to do with |
790 | authentication, it does leverage the same session plugins. Now that |
791 | those plugins are enabled, let's go back and improve the "delete |
792 | and redirect with query parameters" code seen at the end of the |
905a3a26 |
793 | L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD> part of the |
d442cc9f |
794 | tutorial. |
795 | |
796 | First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete> |
3533daff |
797 | to match the following (everything after the model search line of code |
798 | has changed): |
d442cc9f |
799 | |
905a3a26 |
800 | =head2 delete |
1390ef0e |
801 | |
d442cc9f |
802 | Delete a book |
1390ef0e |
803 | |
d442cc9f |
804 | =cut |
1390ef0e |
805 | |
d442cc9f |
806 | sub delete : Local { |
807 | # $id = primary key of book to delete |
808 | my ($self, $c, $id) = @_; |
1390ef0e |
809 | |
d442cc9f |
810 | # Search for the book and then delete it |
d0496197 |
811 | $c->model('DB::Books')->search({id => $id})->delete_all; |
1390ef0e |
812 | |
d442cc9f |
813 | # Use 'flash' to save information across requests until it's read |
814 | $c->flash->{status_msg} = "Book deleted"; |
1390ef0e |
815 | |
3533daff |
816 | # Redirect the user back to the list page |
d442cc9f |
817 | $c->response->redirect($c->uri_for('/books/list')); |
818 | } |
819 | |
1390ef0e |
820 | Next, open C<root/src/wrapper.tt2> and update the TT code to pull from |
d442cc9f |
821 | flash vs. the C<status_msg> query parameter: |
822 | |
1390ef0e |
823 | ... |
d442cc9f |
824 | <div id="content"> |
1390ef0e |
825 | [%# Status and error messages %] |
826 | <span class="message">[% status_msg || c.flash.status_msg %]</span> |
827 | <span class="error">[% error_msg %]</span> |
828 | [%# This is where TT will stick all of your template's contents. -%] |
829 | [% content %] |
830 | </div><!-- end content --> |
831 | ... |
905a3a26 |
832 | |
1390ef0e |
833 | Although the sample above only shows the C<content> div, leave the |
834 | rest of the file intact -- the only change we made to the C<wrapper.tt2> |
835 | was to add "C<|| c.request.params.status_msg>" to the |
836 | C<E<lt>span class="message"E<gt>> line. |
d442cc9f |
837 | |
838 | |
839 | =head2 Try Out Flash |
840 | |
905a3a26 |
841 | Restart the development server and point your browser to |
842 | L<http://localhost:3000/books/url_create/Test/1/4> to create an extra |
843 | several books. Click the "Return to list" link and delete one of the |
844 | "Test" books you just added. The C<flash> mechanism should retain our |
3533daff |
845 | "Book deleted" status message across the redirect. |
d442cc9f |
846 | |
847 | B<NOTE:> While C<flash> will save information across multiple requests, |
848 | I<it does get cleared the first time it is read>. In general, this is |
849 | exactly what you want -- the C<flash> message will get displayed on |
850 | the next screen where it's appropriate, but it won't "keep showing up" |
851 | after that first time (unless you reset it). Please refer to |
852 | L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional |
853 | information. |
854 | |
1390ef0e |
855 | |
3533daff |
856 | =head2 Switch To Flash-To-Stash |
857 | |
905a3a26 |
858 | Although the a use of flash above is certainly an improvement over the |
1390ef0e |
859 | C<status_msg> we employed in Part 4 of the tutorial, the |
860 | C<status_msg || c.flash.status_msg> statement is a little ugly. A nice |
905a3a26 |
861 | alternative is to use the C<flash_to_stash> feature that automatically |
1390ef0e |
862 | copies the content of flash to stash. This makes your controller |
905a3a26 |
863 | and template code work regardless of where it was directly access, a |
864 | forward, or a redirect. To enable C<flash_to_stash>, you can either |
865 | set the value in C<lib/MyApp.pm> by changing the default |
3533daff |
866 | C<__PACKAGE__-E<gt>config> setting to something like: |
867 | |
868 | __PACKAGE__->config( |
869 | name => 'MyApp', |
870 | session => {flash_to_stash => 1} |
871 | ); |
872 | |
45d511e0 |
873 | B<or> add the following to C<myapp.conf>: |
3533daff |
874 | |
45d511e0 |
875 | <session> |
876 | flash_to_stash 1 |
877 | </session> |
3533daff |
878 | |
905a3a26 |
879 | The C<__PACKAGE__-E<gt>config> option is probably preferable here |
880 | since it's not something you will want to change at runtime without it |
3533daff |
881 | possibly breaking some of your code. |
882 | |
1390ef0e |
883 | Then edit C<root/src/wrapper.tt2> and change the C<status_msg> line |
884 | to match the following: |
3533daff |
885 | |
886 | <span class="message">[% status_msg %]</span> |
887 | |
888 | Restart the development server and go to |
905a3a26 |
889 | L<http://localhost:3000/books/list> in your browser. Delete another |
3533daff |
890 | of the "Test" books you added in the previous step. Flash should still |
891 | maintain the status message across the redirect even though you are no |
8a7c5151 |
892 | longer explicitly accessing C<c.flash>. |
3533daff |
893 | |
d442cc9f |
894 | |
895 | =head1 AUTHOR |
896 | |
897 | Kennedy Clark, C<hkclark@gmail.com> |
898 | |
899 | Please report any errors, issues or suggestions to the author. The |
900 | most recent version of the Catalyst Tutorial can be found at |
d712b826 |
901 | L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/>. |
d442cc9f |
902 | |
45c7830f |
903 | Copyright 2006-2008, Kennedy Clark, under Creative Commons License |
95674086 |
904 | (L<http://creativecommons.org/licenses/by-sa/3.0/us/>). |