Merge from depluralization branch
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / Authentication.pod
index d3c46d6..0d7e7ab 100644 (file)
@@ -1,11 +1,11 @@
 =head1 NAME
 
-Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 4: Authentication
+Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Chapter 5: Authentication
 
 
 =head1 OVERVIEW
 
-This is B<Part 4 of 9> for the Catalyst tutorial.
+This is B<Chapter 5 of 10> for the Catalyst tutorial.
 
 L<Tutorial Overview|Catalyst::Manual::Tutorial>
 
@@ -21,30 +21,34 @@ L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
 
 =item 3
 
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
 
 =item 4
 
-B<Authentication>
+L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
 
 =item 5
 
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
+B<Authentication>
 
 =item 6
 
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
+L<Authorization|Catalyst::Manual::Tutorial::Authorization>
 
 =item 7
 
-L<Testing|Catalyst::Manual::Tutorial::Testing>
+L<Debugging|Catalyst::Manual::Tutorial::Debugging>
 
 =item 8
 
-L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+L<Testing|Catalyst::Manual::Tutorial::Testing>
 
 =item 9
 
+L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+
+=item 10
+
 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
 
 =back
@@ -54,14 +58,15 @@ L<Appendices|Catalyst::Manual::Tutorial::Appendices>
 
 Now that we finally have a simple yet functional application, we can
 focus on providing authentication (with authorization coming next in
-Part 5).
+Chapter 6).
 
-This part of the tutorial is divided into two main sections: 1) basic,
+This chapter of the tutorial is divided into two main sections: 1) basic,
 cleartext authentication and 2) hash-based authentication.
 
 You can checkout the source code for this example from the catalyst
 subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro>
+L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
+
 
 =head1 BASIC AUTHENTICATION
 
@@ -73,13 +78,13 @@ application.
 
 First, we add both user and role information to the database (we will
 add the role information here although it will not be used until the
-authorization section, Part 5).  Create a new SQL script file by opening
+authorization section, Chapter 6).  Create a new SQL script file by opening
 C<myapp02.sql> in your editor and insert:
 
     --
-    -- Add users and roles tables, along with a many-to-many join table
+    -- Add user and role tables, along with a many-to-many join table
     --
-    CREATE TABLE users (
+    CREATE TABLE user (
             id            INTEGER PRIMARY KEY,
             username      TEXT,
             password      TEXT,
@@ -88,11 +93,11 @@ C<myapp02.sql> in your editor and insert:
             last_name     TEXT,
             active        INTEGER
     );
-    CREATE TABLE roles (
+    CREATE TABLE role (
             id   INTEGER PRIMARY KEY,
             role TEXT
     );
-    CREATE TABLE user_roles (
+    CREATE TABLE user_role (
             user_id INTEGER,
             role_id INTEGER,
             PRIMARY KEY (user_id, role_id)
@@ -100,75 +105,51 @@ C<myapp02.sql> in your editor and insert:
     --
     -- Load up some initial test data
     --
-    INSERT INTO users VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe',  'Blow', 1);
-    INSERT INTO users VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe',  1);
-    INSERT INTO users VALUES (3, 'test03', 'mypass', 't03@na.com', 'No',   'Go',   0);
-    INSERT INTO roles VALUES (1, 'user');
-    INSERT INTO roles VALUES (2, 'admin');
-    INSERT INTO user_roles VALUES (1, 1);
-    INSERT INTO user_roles VALUES (1, 2);
-    INSERT INTO user_roles VALUES (2, 1);
-    INSERT INTO user_roles VALUES (3, 1);
+    INSERT INTO user VALUES (1, 'test01', 'mypass', 't01@na.com', 'Joe',  'Blow', 1);
+    INSERT INTO user VALUES (2, 'test02', 'mypass', 't02@na.com', 'Jane', 'Doe',  1);
+    INSERT INTO user VALUES (3, 'test03', 'mypass', 't03@na.com', 'No',   'Go',   0);
+    INSERT INTO role VALUES (1, 'user');
+    INSERT INTO role VALUES (2, 'admin');
+    INSERT INTO user_role VALUES (1, 1);
+    INSERT INTO user_role VALUES (1, 2);
+    INSERT INTO user_role VALUES (2, 1);
+    INSERT INTO user_role VALUES (3, 1);
 
 Then load this into the C<myapp.db> database with the following command:
 
     $ sqlite3 myapp.db < myapp02.sql
 
-
 =head2 Add User and Role Information to DBIC Schema
 
-This step adds DBIC-based classes for the user-related database tables
-(the role information will not be used until Part 5):
-
-Edit C<lib/MyAppDB.pm> and update the contents to match (only the
-C<MyAppDB =E<gt> [qw/Book BookAuthor Author User UserRole Role/]> line
-has changed):
-
-    package MyAppDB;
-    
-    =head1 NAME 
-    
-    MyAppDB -- DBIC Schema Class
-    
-    =cut
-    
-    # Our schema needs to inherit from 'DBIx::Class::Schema'
-    use base qw/DBIx::Class::Schema/;
-    
-    # Need to load the DB Model classes here.
-    # You can use this syntax if you want:
-    #    __PACKAGE__->load_classes(qw/Book BookAuthor Author User UserRole Role/);
-    # Also, if you simply want to load all of the classes in a directory
-    # of the same name as your schema class (as we do here) you can use:
-    #    __PACKAGE__->load_classes(qw//);
-    # But the variation below is more flexible in that it can be used to 
-    # load from multiple namespaces.
-    __PACKAGE__->load_classes({
-        MyAppDB => [qw/Book BookAuthor Author User UserRole Role/]
-    });
-    
-    1;
-
+Although we could manually edit the DBIC schema information to include
+the new tables added in the previous step, let's use the C<create=static>
+option on the DBIC model helper to do most of the work for us:
+
+    $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+        create=static components=TimeStamp dbi:SQLite:myapp.db
+     exists "/root/dev/MyApp/script/../lib/MyApp/Model"
+     exists "/root/dev/MyApp/script/../t"
+    Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ...
+    Schema dump completed.
+     exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
+    $
+    $ ls lib/MyApp/Schema/Result
+    Author.pm  BookAuthor.pm  Book.pm  Role.pm  User.pm  UserRole.pm
 
-=head2 Create New "Result Source Objects"
+Notice how the helper has added three new table-specific result source
+files to the C<lib/MyApp/Schema/Result> directory.  And, more
+importantly, even if there were changes to the existing result source
+files, those changes would have only been written above the C<# DO NOT
+MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited
+enhancements would have been preserved.
 
-Create the following three files with the content shown below.
+Speaking of "hand-editted enhancements," we should now add
+relationship information to the three new result source files.  Edit
+each of these files and add the following information between the C<#
+DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>:
 
-C<lib/MyAppDB/User.pm>:
+C<lib/MyApp/Schema/Result/User.pm>:
 
-    package MyAppDB::User;
-    
-    use base qw/DBIx::Class/;
-    
-    # Load required DBIC stuff
-    __PACKAGE__->load_components(qw/PK::Auto Core/);
-    # Set the table name
-    __PACKAGE__->table('users');
-    # Set columns in table
-    __PACKAGE__->add_columns(qw/id username password email_address first_name last_name/);
-    # Set the primary key for the table
-    __PACKAGE__->set_primary_key('id');
-    
     #
     # Set relationships:
     #
@@ -177,42 +158,20 @@ C<lib/MyAppDB/User.pm>:
     #   args:
     #     1) Name of relationship, DBIC will create accessor with this name
     #     2) Name of the model class referenced by this relationship
-    #     3) Column name in *foreign* table
-    __PACKAGE__->has_many(map_user_role => 'MyAppDB::UserRole', 'user_id');
-    
+    #     3) Column name in *foreign* table (aka, foreign key in peer table)
+    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'user_id');
     
-    =head1 NAME
-    
-    MyAppDB::User - A model object representing a person with access to the system.
-    
-    =head1 DESCRIPTION
-    
-    This is an object that represents a row in the 'users' table of your application
-    database.  It uses DBIx::Class (aka, DBIC) to do ORM.
-    
-    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
-    Offline utilities may wish to use this class directly.
-    
-    =cut
-    
-    1;
+    # many_to_many():
+    #   args:
+    #     1) Name of relationship, DBIC will create accessor with this name
+    #     2) Name of has_many() relationship this many_to_many() is shortcut for
+    #     3) Name of belongs_to() relationship in model class of has_many() above
+    #   You must already have the has_many() defined to use a many_to_many().
+    __PACKAGE__->many_to_many(roles => 'map_user_role', 'role');
 
 
-C<lib/MyAppDB/Role.pm>:
+C<lib/MyApp/Schema/Result/Role.pm>:
 
-    package MyAppDB::Role;
-    
-    use base qw/DBIx::Class/;
-    
-    # Load required DBIC stuff
-    __PACKAGE__->load_components(qw/PK::Auto Core/);
-    # Set the table name
-    __PACKAGE__->table('roles');
-    # Set columns in table
-    __PACKAGE__->add_columns(qw/id role/);
-    # Set the primary key for the table
-    __PACKAGE__->set_primary_key('id');
-    
     #
     # Set relationships:
     #
@@ -221,43 +180,12 @@ C<lib/MyAppDB/Role.pm>:
     #   args:
     #     1) Name of relationship, DBIC will create accessor with this name
     #     2) Name of the model class referenced by this relationship
-    #     3) Column name in *foreign* table
-    __PACKAGE__->has_many(map_user_role => 'MyAppDB::UserRole', 'role_id');
-    
-    
-    =head1 NAME
-    
-    MyAppDB::Role - A model object representing a class of access permissions to 
-    the system.
-    
-    =head1 DESCRIPTION
-    
-    This is an object that represents a row in the 'roles' table of your 
-    application database.  It uses DBIx::Class (aka, DBIC) to do ORM.
-    
-    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
-    "Offline" utilities may wish to use this class directly.
-    
-    =cut
-    
-    1;
+    #     3) Column name in *foreign* table (aka, foreign key in peer table)
+    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'role_id');
 
 
-C<lib/MyAppDB/UserRole.pm>:
+C<lib/MyApp/Schema/Result/UserRole.pm>:
 
-    package MyAppDB::UserRole;
-    
-    use base qw/DBIx::Class/;
-    
-    # Load required DBIC stuff
-    __PACKAGE__->load_components(qw/PK::Auto Core/);
-    # Set the table name
-    __PACKAGE__->table('user_roles');
-    # Set columns in table
-    __PACKAGE__->add_columns(qw/user_id role_id/);
-    # Set the primary key for the table
-    __PACKAGE__->set_primary_key(qw/user_id role_id/);
-    
     #
     # Set relationships:
     #
@@ -267,41 +195,32 @@ C<lib/MyAppDB/UserRole.pm>:
     #     1) Name of relationship, DBIC will create accessor with this name
     #     2) Name of the model class referenced by this relationship
     #     3) Column name in *this* table
-    __PACKAGE__->belongs_to(user => 'MyAppDB::User', 'user_id');
+    __PACKAGE__->belongs_to(user => 'MyApp::Schema::Result::User', 'user_id');
     
     # belongs_to():
     #   args:
     #     1) Name of relationship, DBIC will create accessor with this name
     #     2) Name of the model class referenced by this relationship
     #     3) Column name in *this* table
-    __PACKAGE__->belongs_to(role => 'MyAppDB::Role', 'role_id');
-    
-    
-    =head1 NAME
-    
-    MyAppDB::UserRole - A model object representing the JOIN between Users and Roles.
-    
-    =head1 DESCRIPTION
-    
-    This is an object that represents a row in the 'user_roles' table of your application
-    database.  It uses DBIx::Class (aka, DBIC) to do ORM.
-    
-    You probably won't need to use this class directly -- it will be automatically
-    used by DBIC where joins are needed.
-    
-    For Catalyst, this is designed to be used through MyApp::Model::MyAppDB.
-    Offline utilities may wish to use this class directly.
-    
-    =cut
-    
-    1;
+    __PACKAGE__->belongs_to(role => 'MyApp::Schema::Result::Role', 'role_id');
+
+The code for these three sets of updates is obviously very similar to
+the edits we made to the C<Book>, C<Author>, and C<BookAuthor>
+classes created in Chapter 3.
 
-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.
+Note that we do not need to make any change to the
+C<lib/MyApp/Schema.pm> schema file.  It simply tells DBIC to load all
+of the Result Class and ResultSet Class files it finds in below the
+C<lib/MyApp/Schema> directory, so it will automatically pick up our
+new table information.
 
 
 =head2 Sanity-Check Reload of Development Server
 
-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:
+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:
 
     $ script/myapp_server.pl
 
@@ -313,91 +232,130 @@ Look for the three new model objects in the startup debug output:
     +-------------------------------------------------------------------+----------+
     | MyApp::Controller::Books                                          | instance |
     | MyApp::Controller::Root                                           | instance |
-    | MyApp::Model::MyAppDB                                             | instance |
-    | MyApp::Model::MyAppDB::Author                                     | class    |
-    | MyApp::Model::MyAppDB::Book                                       | class    |
-    | MyApp::Model::MyAppDB::BookAuthor                                 | class    |
-    | MyApp::Model::MyAppDB::Role                                       | class    |
-    | MyApp::Model::MyAppDB::User                                       | class    |
-    | MyApp::Model::MyAppDB::UserRole                                   | class    |
+    | MyApp::Model::DB                                                  | instance |
+    | MyApp::Model::DB::Author                                          | class    |
+    | MyApp::Model::DB::Book                                            | class    |
+    | MyApp::Model::DB::BookAuthor                                      | class    |
+    | MyApp::Model::DB::Role                                            | class    |
+    | MyApp::Model::DB::User                                            | class    |
+    | MyApp::Model::DB::UserRole                                        | class    |
     | MyApp::View::TT                                                   | instance |
     '-------------------------------------------------------------------+----------'
     ...
 
-Again, notice that your "result source" classes have been "re-loaded" by Catalyst under C<MyApp::Model>.
+Again, notice that your "Result Class" classes have been "re-loaded"
+by Catalyst under C<MyApp::Model>.
 
 
 =head2 Include Authentication and Session Plugins
 
-Edit C<lib/MyApp.pm> and update it as follows (everything below C<StackTrace> is new):
-
-    use Catalyst qw/
-            -Debug
-            ConfigLoader
-            Static::Simple
-            
-            StackTrace
-            
-            Authentication
-            Authentication::Store::DBIC
-            Authentication::Credential::Password
-            
-            Session
-            Session::Store::FastMmap
-            Session::State::Cookie
-            /;
-
-The three C<Authentication> plugins work together to support
-Authentication while the C<Session> plugins are required to maintain
-state across multiple HTTP requests.  Note that there are several
-options for L<Session::Store|Catalyst::Plugin::Session::Store>
+Edit C<lib/MyApp.pm> and update it as follows (everything below
+C<StackTrace> is new):
+
+    # Load plugins
+    use Catalyst qw/-Debug
+                    ConfigLoader
+                    Static::Simple
+    
+                    StackTrace
+    
+                    Authentication
+    
+                    Session
+                    Session::Store::FastMmap
+                    Session::State::Cookie
+                    /;
+
+B<Note:> As discussed in MoreCatalystBasics, different versions of
+C<Catalyst::Devel> have used a variety of methods to load the plugins.
+You can put the plugins in the C<use Catalyst> statement if you prefer.
+
+The C<Authentication> plugin supports Authentication while the
+C<Session> plugins are required to maintain state across multiple HTTP
+requests.
+
+Note that the only required Authentication class is the main one. This
+is a change that occurred in version 0.09999_01 of the
+C<Authentication> plugin. You B<do not need> to specify a particular
+Authentication::Store or Authentication::Credential plugin. Instead,
+indicate the Store and Credential you want to use in your application
+configuration (see below).
+
+Make sure you include the additional plugins as new dependencies in
+the Makefile.PL file something like this:
+
+    requires (
+        'Catalyst::Plugin::Authentication' => '0',
+        'Catalyst::Plugin::Session' => '0',
+        'Catalyst::Plugin::Session::Store::FastMmap' => '0',
+        'Catalyst::Plugin::Session::State::Cookie' => '0',
+    );
+
+Note that there are several options for
+L<Session::Store|Catalyst::Plugin::Session::Store>
 (L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap>
 is generally a good choice if you are on Unix; try
 L<Session::Store::File|Catalyst::Plugin::Session::Store::File> if you
 are on Win32) -- consult
 L<Session::Store|Catalyst::Plugin::Session::Store> and its subclasses
-for additional information and options (for example to use a
-database-backed session store).
+for additional information and options (for example to use a database-
+backed session store).
 
 
 =head2 Configure Authentication
 
-Although C<__PACKAGE__-E<gt>config(name =E<gt> 'value');> is still
-supported, newer Catalyst applications tend to place all configuration
-information in C<myapp.yml> and automatically load this information into
-C<MyApp-E<gt>config> using the 
-L<ConfigLoader|Catalyst::Plugin::ConfigLoader> plugin.  Here, we need
-to load several parameters that tell 
-L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
-where to locate information in your database.  To do this, edit the 
-C<myapp.yml> YAML and update it to match:
-
-    ---
-    name: MyApp
-    authentication:
-        dbic:
-            # Note this first definition would be the same as setting
-            # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User'
-            # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file).
-            #
-            # This is the model object created by Catalyst::Model::DBIC from your
-            # schema (you created 'MyAppDB::User' but as the Catalyst startup
-            # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User').
-            # NOTE: Omit 'MyApp::Model' to avoid a component lookup issue in Catalyst 5.66
-            user_class: MyAppDB::User
-            # This is the name of the field in your 'users' table that contains the user's name
-            user_field: username
-            # This is the name of the field in your 'users' table that contains the password
-            password_field: password
-            # Other options can go here for hashed passwords
-
-Inline comments in the code above explain how each field is being used.
-
-B<TIP>: Although YAML uses a very simple and easy-to-ready format, it
-does require the use of a consistent level of indenting.  Be sure you
-line up everything on a given 'level' with the same number of indents.
-Also, be sure not to use C<tab> characters (YAML does not support them
-because they are handled inconsistently across editors).
+There are a variety of ways to provide configuration information to
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>.
+Here we will use 
+L<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
+because it automatically sets a reasonable set of defaults for us. Open 
+C<lib/MyApp.pm> and place the following text above the call to
+C<__PACKAGE__-E<gt>setup();>:
+
+    # Configure SimpleDB Authentication
+    __PACKAGE__->config->{'Plugin::Authentication'} = {
+            default => {
+                class           => 'SimpleDB',
+                user_model      => 'DB::User',
+                password_type   => 'clear',
+            },
+        };
+
+We could have placed this configuration in C<myapp.conf>, but placing 
+it in C<lib/MyApp.pm> is probably a better place since it's not likely 
+something that users of your application will want to change during 
+deployment (or you could use a mixture: leave C<class> and 
+C<user_model> defined in C<lib/MyApp.pm> as we show above, but place 
+C<password_type> in C<myapp.conf> to allow the type of password to be 
+easily modified during deployment).  We will stick with putting 
+all of the authentication-related configuration in C<lib/MyApp.pm> 
+for the tutorial, but if you wish to use C<myapp.conf>, just convert
+to the following code:
+
+    <Plugin::Authentication>
+        use_session 1
+        <default>
+            password_type self_check
+            user_model    DB::User
+            class         SimpleDB
+        </default>
+    </Plugin::Authentication>
+
+B<TIP:> Here is a short script that will dump the contents of 
+C<MyApp->config> to L<Config::General|Config::General> format in
+C<myapp.conf>:
+
+    $ perl -Ilib -e 'use MyApp; use Config::General; 
+        Config::General->new->save_file("myapp.conf", MyApp->config);'
+
+B<NOTE:> Because we are using SimpleDB along with a database layout 
+that complies with its default assumptions, we don't need to specify
+the names of the columns where our username and password information
+is stored (hence, the "Simple" part of "SimpleDB").  That being said,
+SimpleDB lets you specify that type of information if you need to.
+Take a look at 
+C<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
+for details.
 
 
 =head2 Add Login and Logout Controllers
@@ -407,18 +365,16 @@ Use the Catalyst create script to create two stub controller files:
     $ script/myapp_create.pl controller Login
     $ script/myapp_create.pl controller Logout
 
-B<NOTE>: You could easily use a single controller here.  For example,
-you could have a C<User> controller with both C<login> and C<logout>
-actions.  Remember, Catalyst is designed to be very flexible, and leaves
-such matters up to you, the designer and programmer.
-
-Then open C<lib/MyApp/Controller/Login.pm>, locate the C<sub index : 
-Private> method (this was automatically inserted by the helpers when we 
-created the Login controller above), and delete this line:
+You could easily use a single controller here.  For example, you could
+have a C<User> controller with both C<login> and C<logout> actions.
+Remember, Catalyst is designed to be very flexible, and leaves such
+matters up to you, the designer and programmer.
 
-    $c->response->body('Matched MyApp::Controller::Login in Login.');
-
-Then update it to match:
+Then open C<lib/MyApp/Controller/Login.pm>, locate the
+C<sub index :Path :Args(0)> method (or C<sub index : Private> if you
+are using an older version of Catalyst) that was automatically
+inserted by the helpers when we created the Login controller above,
+and update the definition of C<sub index> to match:
 
     =head2 index
     
@@ -426,7 +382,7 @@ Then update it to match:
     
     =cut
     
-    sub index : Private {
+    sub index :Path :Args(0) {
         my ($self, $c) = @_;
     
         # Get the username and password from form
@@ -436,9 +392,11 @@ Then update it to match:
         # If the username and password values were found in form
         if ($username && $password) {
             # Attempt to log the user in
-            if ($c->login($username, $password)) {
+            if ($c->authenticate({ username => $username,
+                                   password => $password  } )) {
                 # If successful, then let them use the application
-                $c->response->redirect($c->uri_for('/books/list'));
+                $c->response->redirect($c->uri_for(
+                    $c->controller('Books')->action_for('list')));
                 return;
             } else {
                 # Set an error message
@@ -450,33 +408,35 @@ Then update it to match:
         $c->stash->{template} = 'login.tt2';
     }
 
+Be sure to remove the C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Login in Login.');>
+line of the C<sub index>.
+
 This controller fetches the C<username> and C<password> values from the
-login form and attempts to perform a login.  If successful, it redirects
-the user to the book list page.  If the login fails, the user will stay
-at the login page but receive an error message.  If the C<username> and
-C<password> values are not present in the form, the user will be taken
-to the empty login form.
-
-Note that we could have used something like C<sub default :Private>; 
-however, the use of C<default> actions is discouraged because it does
-not receive path args as with other actions.  The recommended practice 
-is to only use C<default> in C<MyApp::Controller::Root>.
-
-Another option would be to use something like 
-C<sub base :Path :Args(0) {...}> (where the C<...> refers to the login 
-code shown in C<sub index : Private> above). We are using C<sub base 
-:Path :Args(0) {...}> here to specifically match the URL C</login>. 
-C<Path> actions (aka, "literal actions") create URI matches relative to 
-the namespace of the controller where they are defined.  Although 
-C<Path> supports arguments that allow relative and absolute paths to be 
-defined, here we use an empty C<Path> definition to match on just the 
-name of the controller itself.  The method name, C<base>, is arbitrary. 
-We make the match even more specific with the C<:Args(0)> action 
-modifier -- this forces the match on I<only> C</login>, not 
+login form and attempts to authenticate the user.  If successful, it
+redirects the user to the book list page.  If the login fails, the user
+will stay at the login page and receive an error message.  If the
+C<username> and C<password> values are not present in the form, the
+user will be taken to the empty login form.
+
+Note that we could have used something like "C<sub default :Path>",
+however, it is generally recommended (partly for historical reasons,
+and partly for code clarity) only to use C<default> in
+C<MyApp::Controller::Root>, and then mainly to generate the 404 not
+found page for the application.
+
+Instead, we are using "C<sub somename :Path :Args(0) {...}>" here to
+specifically match the URL C</login>. C<Path> actions (aka, "literal
+actions") create URI matches relative to the namespace of the
+controller where they are defined.  Although C<Path> supports
+arguments that allow relative and absolute paths to be defined, here
+we use an empty C<Path> definition to match on just the name of the
+controller itself.  The method name, C<index>, is arbitrary. We make
+the match even more specific with the C<:Args(0)> action modifier --
+this forces the match on I<only> C</login>, not
 C</login/somethingelse>.
 
-Next, update the corresponding method in C<lib/MyApp/Controller/Logout.pm>
-to match:
+Next, update the corresponding method in
+C<lib/MyApp/Controller/Logout.pm> to match:
 
     =head2 index
     
@@ -484,7 +444,7 @@ to match:
     
     =cut
     
-    sub index : Private {
+    sub index :Path :Args(0) {
         my ($self, $c) = @_;
     
         # Clear the user's state
@@ -494,8 +454,8 @@ to match:
         $c->response->redirect($c->uri_for('/'));
     }
 
-As with the login controller, be sure to delete the 
-C<$c->response->body('Matched MyApp::Controller::Logout in Logout.');>
+As with the login controller, be sure to delete the
+C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Logout in Logout.');>
 line of the C<sub index>.
 
 
@@ -506,7 +466,7 @@ Create a login form by opening C<root/src/login.tt2> and inserting:
     [% META title = 'Login' %]
     
     <!-- Login form -->
-    <form method="post" action=" [% Catalyst.uri_for('/login') %] ">
+    <form method="post" action="[% c.uri_for('/login') %]">
       <table>
         <tr>
           <td>Username:</td>
@@ -542,16 +502,16 @@ the following method:
     =cut
     
     # Note that 'auto' runs after 'begin' but before your actions and that
-    # 'auto' "chain" (all from application path to most specific class are run)
+    # 'auto's "chain" (all from application path to most specific class are run)
     # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
     sub auto : Private {
         my ($self, $c) = @_;
     
         # Allow unauthenticated users to reach the login page.  This
-        # allows anauthenticated users to reach any action in the Login
+        # allows unauthenticated users to reach any action in the Login
         # controller.  To lock it down to a single action, we could use:
         #   if ($c->action eq $c->controller('Login')->action_for('index'))
-        # to only allow unauthenticated access to the C<index> action we
+        # to only allow unauthenticated access to the 'index' action we
         # added above.
         if ($c->controller eq $c->controller('Login')) {
             return 1;
@@ -571,44 +531,14 @@ the following method:
         return 1;
     }
 
-B<Note:> Catalyst provides a number of different types of actions, such
-as C<Local>, C<Regex>, and C<Private>.  You should refer to
-L<Catalyst::Manual::Intro> for a more detailed explanation, but the
-following bullet points provide a quick introduction:
-
-=over 4
-
-=item *
-
-The majority of application use C<Local> actions for items that respond
-to user requests and C<Private> actions for those that do not directly
-respond to user input.
-
-=item *
-
-There are five types of C<Private> actions: C<begin>, C<end>,
-C<default>, C<index>, and C<auto>.
-
-=item *
-
-With C<begin>, C<end>, C<default>, C<index> private actions, only the
-most specific action of each type will be called.  For example, if you
-define a C<begin> action in your controller it will I<override> a 
-C<begin> action in your application/root controller -- I<only> the
-action in your controller will be called.
-
-=item *
-
-Unlike the other actions where only a single method is called for each 
-request, I<every> auto action along the chain of namespaces will be 
-called.  Each C<auto> action will be called I<from the application/root
-controller down through the most specific class>.
-
-=back
-
-By placing the authentication enforcement code inside the C<auto> method
-of C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be
-called for I<every> request that is received by the entire application.
+As discussed in
+L<Catalyst::Manual::Tutorial::MoreCatalystBasics/CREATE A CATALYST CONTROLLER>,
+every C<auto> method from the application/root controller down to the
+most specific controller will be called.  By placing the
+authentication enforcement code inside the C<auto> method of
+C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be
+called for I<every> request that is received by the entire
+application.
 
 
 =head2 Displaying Content Only to Authenticated Users
@@ -618,24 +548,26 @@ changes depending on whether the user has authenticated yet.  To do
 this, open C<root/src/login.tt2> in your editor and add the following
 lines to the bottom of the file:
 
+    ...
     <p>
     [%
-       # This code illustrates how certain parts of the TT 
+       # This code illustrates how certain parts of the TT
        # template will only be shown to users who have logged in
     %]
-    [% IF Catalyst.user_exists %]
-        Please Note: You are already logged in as '[% Catalyst.user.username %]'.
-        You can <a href="[% Catalyst.uri_for('/logout') %]">logout</a> here.
+    [% IF c.user_exists %]
+        Please Note: You are already logged in as '[% c.user.username %]'.
+        You can <a href="[% c.uri_for('/logout') %]">logout</a> here.
     [% ELSE %]
         You need to log in to use this application.
     [% END %]
     [%#
        Note that this whole block is a comment because the "#" appears
-       immediate after the "[%" (with no spaces in between).  Although it 
-       can be a handy way to temporarily "comment out" a whole block of 
-       TT code, it's probably a little too subtle for use in "normal" 
+       immediate after the "[%" (with no spaces in between).  Although it
+       can be a handy way to temporarily "comment out" a whole block of
+       TT code, it's probably a little too subtle for use in "normal"
        comments.
     %]
+    </p>
 
 Although most of the code is comments, the middle few lines provide a
 "you are already logged in" reminder if the user returns to the login
@@ -651,38 +583,47 @@ running) and restart it:
 
     $ script/myapp_server.pl
 
-B<IMPORTANT NOTE>: If you happen to be using Internet Explorer, you may
-need to use the command C<script/myapp_server.pl -k> to enable the
-keepalive feature in the development server.  Otherwise, the HTTP
-redirect on successful login may not work correctly with IE (it seems to
-work without -k if you are running the web browser and development
-server on the same machine).  If you are using browser a browser other
-than IE, it should work either way.  If you want to make keepalive the
-default, you can edit C<script/myapp_server.pl> and change the
-initialization value for C<$keepalive> to C<1>.  (You will need to do
-this every time you create a new Catalyst application or rebuild the
-C<myapp_server.pl> script.)
+B<IMPORTANT NOTE:> If you are having issues with authentication on
+Internet Explorer, be sure to check the system clocks on both your
+server and client machines.  Internet Explorer is very picky about
+timestamps for cookies.  You can quickly sync a Debian system by
+installing the "ntpdate" package:
+
+    sudo aptitude -y install ntpdate
+
+And then run the following command:
+
+    sudo ntpdate-debian
+
+Or, depending on your firewall configuration:
+
+    sudo ntpdate-debian -u
+
+Note: NTP can be a little more finicky about firewalls because it uses
+UDP vs. the more common TCP that you see with most Internet protocols.
+Worse case, you might have to manually set the time on your development
+box instead of using NTP.
 
 Now trying going to L<http://localhost:3000/books/list> and you should
-be redirected to the login page, hitting Shift+Reload if necessary (the
-"You are already logged in" message should I<not> appear -- if it does,
-click the C<logout> button and try again). Note the C<***Root::auto User
-not found...> debug message in the development server output.  Enter
-username C<test01> and password C<mypass>, and you should be taken to
-the Book List page.
+be redirected to the login page, hitting Shift+Reload or Ctrl+Reload
+if necessary (the "You are already logged in" message should I<not>
+appear -- if it does, click the C<logout> button and try again). Note
+the C<***Root::auto User not found...> debug message in the
+development server output.  Enter username C<test01> and password
+C<mypass>, and you should be taken to the Book List page.
 
 Open C<root/src/books/list.tt2> and add the following lines to the
-bottom:
+bottom (below the closing </table> tag):
 
     <p>
-      <a href="[% Catalyst.uri_for('/login') %]">Login</a>
-      <a href="[% Catalyst.uri_for('form_create') %]">Create</a>
+      <a href="[% c.uri_for('/login') %]">Login</a>
+      <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
     </p>
 
-Reload your browser and you should now see a "Login" and "Create" links 
-at the bottom of the page (as mentioned earlier, you can update template 
-files without reloading the development server).  Click the first link 
-to return to the login page.  This time you I<should> see the "You are 
+Reload your browser and you should now see a "Login" and "Create" links
+at the bottom of the page (as mentioned earlier, you can update template
+files without reloading the development server).  Click the first link
+to return to the login page.  This time you I<should> see the "You are
 already logged in" message.
 
 Finally, click the C<You can logout here> link on the C</login> page.
@@ -692,90 +633,153 @@ need to log in to use this application."
 
 =head1 USING PASSWORD HASHES
 
-In this section we increase the security of our system by converting
-from cleartext passwords to SHA-1 password hashes.
+In this section we increase the security of our system by converting 
+from cleartext passwords to SHA-1 password hashes that include a 
+random "salt" value to make them extremely difficult to crack with
+dictionary and "rainbow table" attacks.
 
 B<Note:> This section is optional.  You can skip it and the rest of the
 tutorial will function normally.
 
-Note that even with the techniques shown in this section, the browser
+Be aware that even with the techniques shown in this section, the browser
 still transmits the passwords in cleartext to your application.  We are
 just avoiding the I<storage> of cleartext passwords in the database by
-using a SHA-1 hash. If you are concerned about cleartext passwords
+using a salted SHA-1 hash. If you are concerned about cleartext passwords
 between the browser and your application, consider using SSL/TLS, made
 easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
 
 
-=head2 Get a SHA-1 Hash for the Password
+=head2 Install DBIx::Class::EncodedColumn
 
-Catalyst uses the C<Digest> module to support a variety of hashing
-algorithms.  Here we will use SHA-1 (SHA = Secure Hash Algorithm).
-First, we should compute the SHA-1 hash for the "mypass" password we are
-using.  The following command-line Perl script provides a "quick and
-dirty" way to do this:
+L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> provides features
+that can greatly simplify the maintenance of passwords.  It's currently 
+not available as a .deb package in the normal Debian repositories, so let's
+install it directly from CPAN:
 
-    $ perl -MDigest::SHA -e 'print Digest::SHA::sha1_hex("mypass"), "\n"'
-    e727d1464ae12436e899a726da5b2f11d8381b26
-    $
+    $ sudo cpan DBIx::Class::EncodedColumn
 
-B<Note:> You should probably modify this code for production use to
-not read the password from the command line.  By having the script
-prompt for the cleartext password, it avoids having the password linger
-in forms such as your C<.bash_history> files (assuming you are using
-BASH as your shell).  An example of such a script can be found in
-Appendix 3.
 
+=head2 Re-Run the DBIC::Schema Model Helper to Include DBIx::Class::EncodedColumn
 
-=head2 Switch to SHA-1 Password Hashes in the Database
+Next, we can re-run the model helper to have it include 
+L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> in all of the 
+Result Classes it generates for us.  Simply use the same command we 
+saw in Chapters 3 and 4, but add C<,EncodedColumn> to the C<components>
+argument:
 
-Next, we need to change the C<password> column of our C<users> table to
-store this hash value vs. the existing cleartext password.  Open
-C<myapp03.sql> in your editor and enter:
+    $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+        create=static components=TimeStamp,EncodedColumn dbi:SQLite:myapp.db
 
-    --
-    -- Convert passwords to SHA-1 hashes
-    --
-    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1;
-    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2;
-    UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3;
-
-Then use the following command to update the SQLite database:
-
-    $ sqlite3 myapp.db < myapp03.sql
-
-B<Note:> We are using SHA-1 hashes here, but many other hashing
-algorithms are supported.  See C<Digest> for more information.
-
-
-=head2 Enable SHA-1 Hash Passwords in
-C<Catalyst::Plugin::Authentication::Store::DBIC>
-
-Edit C<myapp.yml> and update it to match (the C<password_type> and
-C<password_hash_type> are new, everything else is the same):
-
-    ---
-    name: MyApp
-    authentication:
-        dbic:
-            # Note this first definition would be the same as setting
-            # __PACKAGE__->config->{authentication}->{dbic}->{user_class} = 'MyAppDB::User'
-            # in lib/MyApp.pm (IOW, each hash key becomes a "name:" in the YAML file).
-            #
-            # This is the model object created by Catalyst::Model::DBIC from your
-            # schema (you created 'MyAppDB::User' but as the Catalyst startup
-            # debug messages show, it was loaded as 'MyApp::Model::MyAppDB::User').
-            # NOTE: Omit 'MyApp::Model' here just as you would when using 
-            # '$c->model("MyAppDB::User)'
-            user_class: MyAppDB::User
-            # This is the name of the field in your 'users' table that contains the user's name
-            user_field: username
-            # This is the name of the field in your 'users' table that contains the password
-            password_field: password
-            # Other options can go here for hashed passwords
-            # Enabled hashed passwords
-            password_type: hashed
-            # Use the SHA-1 hashing algorithm
-            password_hash_type: SHA-1
+If you then open one of the Result Classes, you will see that it 
+includes EncodedColumn in the C<load_components> line.  Take a look at 
+C<lib/MyApp/Schema/Result/User.pm> since that's the main class where we
+want to use hashed and salted passwords:
+
+    __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
+
+
+=head2 Modify the "password" Column to Use EncodedColumn
+
+Open the file C<lib/MyApp/Schema/Result/User.pm> and enter the following
+text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above
+the closing "1;":
+
+    # Have the 'password' column use a SHA-1 hash and 10-character salt
+    # with hex encoding; Generate the 'check_password" method
+    __PACKAGE__->add_columns(
+        'password' => {
+            data_type           => "TEXT",
+            size                => undef,
+            encode_column       => 1,
+            encode_class        => 'Digest',
+            encode_args         => {salt_length => 10},
+            encode_check_method => 'check_password',
+        },
+    );
+
+This redefines the automatically generated definition for the password 
+fields at the top of the Result Class file to now use EncodedColumn 
+logic (C<encoded_column> is set to 1).  C<encode_class> can be set to 
+either C<Digest> to use 
+L<DBIx::Class::EncodedColumn::Digest|DBIx::Class::EncodedColumn::Digest>, 
+or C<Crypt::Eksblowfish::Bcrypt> for 
+L<DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt|DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt>.
+C<encode_args> is then used to customize the type of Digest you 
+selected. Here we only specified the size of the salt to use, but
+we could have also modified the hashing algorithm ('SHA-256' is 
+the default) and the format to use ('base64' is the default, but
+'hex' and 'binary' are other options).  To use these, you could 
+change the C<encode_args> to something like:
+
+            encode_args         => {algorithm => 'SHA-1', 
+                                    format => 'hex', 
+                                    salt_length => 10},
+
+
+=head2 Load Hashed Passwords in the Database
+
+Next, let's create a quick script to load some hashed and salted passwords
+into the C<password> column of our C<users> table.  Open the file
+C<set_hashed_passwords.pl> in your editor and enter the following text:
+
+    #!/usr/bin/perl
+    
+    use strict;
+    use warnings;
+    
+    use MyApp::Schema;
+    
+    my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db');
+    
+    my @users = $schema->resultset('User')->all;
+    
+    foreach my $user (@users) {
+        $user->password('mypass');
+        $user->update;
+    }
+
+EncodedColumn lets us simple call C<$user->check_password($password)> 
+to see if the user has supplied the correct password, or, as we show 
+above, call C<$user->update($new_password)> to update the hashed 
+password stored for this user.
+
+Then run the following command:
+
+    $ perl -Ilib set_hashed_passwords.pl
+
+We had to use the C<-Ilib> arguement to tell perl to look under the 
+C<lib> directory for our C<MyApp::Schema> model.
+
+Then dump the users table to verify that it worked:
+
+    $ sqlite3 myapp.db "select * from user"
+    1|test01|38d3974fa9e9263099f7bc2574284b2f55473a9bM=fwpX2NR8|t01@na.com|Joe|Blow|1
+    2|test02|6ed8586587e53e0d7509b1cfed5df08feadc68cbMJlnPyPt0I|t02@na.com|Jane|Doe|1
+    3|test03|af929a151340c6aed4d54d7e2651795d1ad2e2f7UW8dHoGv9z|t03@na.com|No|Go|0
+
+As you can see, the passwords are much harder to steal from the 
+database.  Also note that this demonstrates how to use a DBIx::Class 
+model outside of your web application -- a very useful feature in many 
+situations.
+
+
+=head2 Enable Hashed and Salted Passwords
+
+Edit C<lib/MyApp.pm> and update it to match the following text (the only change
+is to the C<password_type> field):
+
+    # Configure SimpleDB Authentication
+    __PACKAGE__->config->{'Plugin::Authentication'} = {
+            default => {
+                class           => 'SimpleDB',
+                user_model      => 'DB::User',
+                password_type   => 'self_check',
+            },
+        };
+
+The use of C<self_check> will cause 
+Catalyst::Plugin::Authentication::Store::DBIC to call the 
+C<check_password> method we enabled on our C<password> columns.
 
 
 =head2 Try Out the Hashed Passwords
@@ -786,75 +790,72 @@ running) and restart it:
     $ script/myapp_server.pl
 
 You should now be able to go to L<http://localhost:3000/books/list> and
-login as before.  When done, click the "Logout" link on the login page
+login as before.  When done, click the "logout" link on the login page
 (or point your browser at L<http://localhost:3000/logout>).
 
-B<Note:> If you receive the debug screen in your browser with a 
-C<Can't call method "stash" on an undefined value...> error message,
-make sure that you are using v0.07 of 
-L<Catalyst::Plugin::Authorization::ACL|Catalyst::Plugin::Authorization::ACL>.
-The following command can be a useful way to quickly dump the version number
-of this module on your system:
-
-    perl -MCatalyst::Plugin::Authorization::ACL -e 'print $Catalyst::Plugin::Authorization::ACL::VERSION, "\n";'
-
 
 =head1 USING THE SESSION FOR FLASH
 
-As discussed in Part 3 of the tutorial, C<flash> allows you to set
-variables in a way that is very similar to C<stash>, but it will 
-remain set across multiple requests.  Once the value is read, it
-is cleared (unless reset).  Although C<flash> has nothing to do with
-authentication, it does leverage the same session plugins.  Now that
-those plugins are enabled, let's go back and improve the "delete
-and redirect with query parameters" code seen at the end of the
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD> part of the 
-tutorial.
+As discussed in the previous chapter of the tutorial, C<flash> allows 
+you to set variables in a way that is very similar to C<stash>, but it 
+will remain set across multiple requests.  Once the value is read, it 
+is cleared (unless reset).  Although C<flash> has nothing to do with 
+authentication, it does leverage the same session plugins.  Now that 
+those plugins are enabled, let's go back and update the "delete and 
+redirect with query parameters" code seen at the end of the L<Basic 
+CRUD|Catalyst::Manual::Tutorial::BasicCRUD> chapter of the tutorial to 
+take advantage of C<flash>.
 
 First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete>
-to match the following:
+to match the following (everything after the model search line of code
+has changed):
 
-    =head2 delete 
+    =head2 delete
     
     Delete a book
-        
+    
     =cut
     
-    sub delete : Local {
-        # $id = primary key of book to delete
-        my ($self, $c, $id) = @_;
+    sub delete :Chained('object') :PathPart('delete') :Args(0) {
+        my ($self, $c) = @_;
     
-        # Search for the book and then delete it
-        $c->model('MyAppDB::Book')->search({id => $id})->delete_all;
+        # Use the book object saved by 'object' and delete it along
+        # with related 'book_authors' entries
+        $c->stash->{object}->delete;
     
         # Use 'flash' to save information across requests until it's read
         $c->flash->{status_msg} = "Book deleted";
-            
-        # Redirect the user back to the list page with status msg as an arg
-        $c->response->redirect($c->uri_for('/books/list'));
+    
+        # Redirect the user back to the list page
+        $c->response->redirect($c->uri_for($self->action_for('list')));
     }
 
-Next, open C<root/lib/site/layout> and update the TT code to pull from 
+Next, open C<root/src/wrapper.tt2> and update the TT code to pull from
 flash vs. the C<status_msg> query parameter:
 
-    <div id="header">[% PROCESS site/header %]</div>
-    
+    ...
     <div id="content">
-    <span class="message">[% status_msg || Catalyst.flash.status_msg %]</span>
-    <span class="error">[% error_msg %]</span>
-    [% content %]
-    </div>
-    
-    <div id="footer">[% PROCESS site/footer %]</div>
+        [%# Status and error messages %]
+        <span class="message">[% status_msg || c.flash.status_msg %]</span>
+        <span class="error">[% error_msg %]</span>
+        [%# This is where TT will stick all of your template's contents. -%]
+        [% content %]
+    </div><!-- end content -->
+    ...
+
+Although the sample above only shows the C<content> div, leave the
+rest of the file intact -- the only change we made to the C<wrapper.tt2>
+was to add "C<|| c.request.params.status_msg>" to the
+C<E<lt>span class="message"E<gt>> line.
 
 
 =head2 Try Out Flash
 
-Restart the development server and point your browser to 
+Restart the development server, log in, and then point your browser to
 L<http://localhost:3000/books/url_create/Test/1/4> to create an extra
-book.  Click the "Return to list" link and delete the "Test" book you
-just added.  The C<flash> mechanism should retain our "Book deleted" 
-status message across the redirect.
+several books.  Click the "Return to list" link and delete one of the
+"Test" books you just added.  The C<flash> mechanism should retain our
+"Book deleted" status message across the redirect.
 
 B<NOTE:> While C<flash> will save information across multiple requests,
 I<it does get cleared the first time it is read>.  In general, this is
@@ -865,13 +866,51 @@ L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional
 information.
 
 
+=head2 Switch To Flash-To-Stash
+
+Although the a use of flash above works well, the
+C<status_msg || c.flash.status_msg> statement is a little ugly. A nice
+alternative is to use the C<flash_to_stash> feature that automatically
+copies the content of flash to stash.  This makes your controller
+and template code work regardless of where it was directly access, a
+forward, or a redirect.  To enable C<flash_to_stash>, you can either
+set the value in C<lib/MyApp.pm> by changing the default
+C<__PACKAGE__-E<gt>config> setting to something like:
+
+    __PACKAGE__->config(
+            name    => 'MyApp',
+            session => {flash_to_stash => 1}
+        );
+
+B<or> add the following to C<myapp.conf>:
+
+    <session>
+        flash_to_stash   1
+    </session>
+
+The C<__PACKAGE__-E<gt>config> option is probably preferable here
+since it's not something you will want to change at runtime without it
+possibly breaking some of your code.
+
+Then edit C<root/src/wrapper.tt2> and change the C<status_msg> line
+to match the following:
+
+    <span class="message">[% status_msg %]</span>
+
+Restart the development server and go to
+L<http://localhost:3000/books/list> in your browser.  Delete another
+of the "Test" books you added in the previous step.  Flash should still
+maintain the status message across the redirect even though you are no
+longer explicitly accessing C<c.flash>.
+
+
 =head1 AUTHOR
 
 Kennedy Clark, C<hkclark@gmail.com>
 
 Please report any errors, issues or suggestions to the author.  The
 most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/>.
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
 
-Copyright 2006, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).