POD typo corrected.
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / Authentication.pod
index b670e67..8267025 100644 (file)
@@ -1,11 +1,11 @@
 =head1 NAME
 
-Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Part 5: Authentication
+Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Chapter 5: Authentication
 
 
 =head1 OVERVIEW
 
-This is B<Part 5 of 10> for the Catalyst tutorial.
+This is B<Chapter 5 of 10> for the Catalyst tutorial.
 
 L<Tutorial Overview|Catalyst::Manual::Tutorial>
 
@@ -58,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 6).
+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
 
@@ -77,7 +78,7 @@ 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 6).  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:
 
     --
@@ -125,24 +126,30 @@ 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 dbi:SQLite:myapp.db
-    $ ls lib/MyApp/Schema
+    $ 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
     Authors.pm  BookAuthors.pm  Books.pm  Roles.pm  UserRoles.pm  Users.pm
 
 Notice how the helper has added three new table-specific result source
-files to the C<lib/MyApp/Schema/MyApp> directory.  And, more
+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-editted
+MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited
 enhancements would have been preserved.
 
-
-Speaking of "hand-editted enhancements," we should now add
+Speaking of "hand-edit ted 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/MyApp/Schema/Users.pm>:
+C<lib/MyApp/Schema/Result/Users.pm>:
 
     #
     # Set relationships:
@@ -152,8 +159,8 @@ C<lib/MyApp/Schema/Users.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 => 'MyApp::Schema::UserRoles', 'user_id');
+    #     3) Column name in *foreign* table (aka, foreign key in peer table)
+    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRoles', 'user_id');
 
     # many_to_many():
     #   args:
@@ -164,7 +171,7 @@ C<lib/MyApp/Schema/Users.pm>:
     __PACKAGE__->many_to_many(roles => 'map_user_role', 'role');
 
 
-C<lib/MyApp/Schema/Roles.pm>:
+C<lib/MyApp/Schema/Result/Roles.pm>:
 
     #
     # Set relationships:
@@ -174,11 +181,11 @@ C<lib/MyApp/Schema/Roles.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 => 'MyApp::Schema::UserRoles', 'role_id');
+    #     3) Column name in *foreign* table (aka, foreign key in peer table)
+    __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRoles', 'role_id');
 
 
-C<lib/MyApp/Schema/UserRoles.pm>:
+C<lib/MyApp/Schema/Result/UserRoles.pm>:
 
     #
     # Set relationships:
@@ -189,25 +196,25 @@ C<lib/MyApp/Schema/UserRoles.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 => 'MyApp::Schema::Users', 'user_id');
+    __PACKAGE__->belongs_to(user => 'MyApp::Schema::Result::Users', '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 => 'MyApp::Schema::Roles', 'role_id');
+    __PACKAGE__->belongs_to(role => 'MyApp::Schema::Result::Roles', 'role_id');
 
 
 The code for these three sets of updates is obviously very similar to
 the edits we made to the C<Books>, C<Authors>, and C<BookAuthors>
-classes created in Part 3.
+classes created in Chapter 3.
 
 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 files it finds in below the
-C<lib/MyApp/Schema> directory, so it will automatically pick
-up our new table information.
+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
@@ -238,7 +245,7 @@ Look for the three new model objects in the startup debug output:
     '-------------------------------------------------------------------+----------'
     ...
 
-Again, notice that your "result class" classes have been "re-loaded"
+Again, notice that your "Result Class" classes have been "re-loaded"
 by Catalyst under C<MyApp::Model>.
 
 
@@ -247,19 +254,23 @@ by Catalyst under C<MyApp::Model>.
 Edit C<lib/MyApp.pm> and update it as follows (everything below
 C<StackTrace> is new):
 
-    use Catalyst qw/
-            -Debug
-            ConfigLoader
-            Static::Simple
+    # Load plugins
+    use Catalyst qw/-Debug
+                ConfigLoader
+                Static::Simple
 
-            StackTrace
+                StackTrace
 
-            Authentication
+                Authentication
 
-            Session
-            Session::Store::FastMmap
-            Session::State::Cookie
-            /;
+                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
@@ -291,19 +302,27 @@ information in C<myapp.conf> and automatically load this information
 into C<MyApp-E<gt>config> using the
 L<ConfigLoader|Catalyst::Plugin::ConfigLoader> plugin.
 
-First, as noted in Part 3 of the tutorial, Catalyst has recently
+As discussed in Chapter 3 of the tutorial, Catalyst has recently
 switched from a default config file format of YAML to
-C<Config::General> (an apache-like format).  In case you are using a
-version of Catalyst earlier than v5.7014, delete the C<myapp.yml>, or
-convert it to .conf format using the TIP in
-L<Catalyst::Manual::MoreCatalystBasics>; then simply follow the
-directions below to create a new C<myapp.conf> file.
+L<Config::General|Config::General> (an apache-like format).  In case
+you are using a version of Catalyst earlier than v5.7014, delete the
+C<myapp.yml>, or convert it to .conf format using the TIP in
+L<Catalyst::Manual::MoreCatalystBasics/EDIT THE LIST OF CATALYST PLUGINS>
+then simply follow the directions below to create a new C<myapp.conf>
+file.  Although we will use the C<Config::General> format here because
+YAML files can be difficult to cut and paste in certain environments,
+you are free to use any format supported by
+L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> and
+L<Config::Any|Config::Any> -- Catalyst will transparently handle the
+different formats.
 
 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.conf> file and update it to match:
 
+    # rename this file to MyApp.yml and put a : in front of "name" if
+    # you want to use yaml like in old versions of Catalyst
     name MyApp
     <authentication>
         default_realm dbic
@@ -326,9 +345,9 @@ C<myapp.conf> file and update it to match:
                     # Use DBIC to retrieve username, password & role information
                     class DBIx::Class
                     # This is the model object created by Catalyst::Model::DBIC
-                    # from your schema (you created 'MyApp::Schema::User' but as
-                    # the Catalyst startup debug messages show, it was loaded as
-                    # 'MyApp::Model::DB::Users').
+                    # from your schema (you created 'MyApp::Schema::Result::User'
+                    # but as the Catalyst startup debug messages show, it was
+                    # loaded as 'MyApp::Model::DB::Users').
                     # NOTE: Omit 'MyApp::Model' here just as you would when using
                     # '$c->model("DB::Users)'
                     user_class DB::Users
@@ -339,9 +358,6 @@ C<myapp.conf> file and update it to match:
 
 Inline comments in the code above explain how each field is being used.
 
-Note that you can use many other config file formats with catalyst.
-See L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
-for details.
 
 =head2 Add Login and Logout Controllers
 
@@ -350,20 +366,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
-: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 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
 
@@ -384,7 +396,8 @@ Then update it to match:
             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
@@ -403,13 +416,13 @@ 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 partly for historical reasons, and partly for code clarity it
-is generally recommended only to use C<default> in
+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 base :Path :Args(0) {...}> here to
+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
@@ -493,7 +506,7 @@ the following method:
         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 'index' action we
@@ -516,63 +529,14 @@ the following method:
         return 1;
     }
 
-
-B<Note:> Catalyst provides a number of different types of actions,
-such as C<Local>, C<Regex>, C<Private> and the new C<Path>.  You
-should refer to L<Catalyst::Manual::Intro|Catalyst::Manual::Intro> for
-a more detailed explanation, but the following bullet points provide a
-quick introduction:
-
-=over 4
-
-=item *
-
-The majority of application have traditionally used 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 *
-
-Newer Catalyst applications tend to use C<Path> actions and the
-C<Args> attribute because of their power and flexibility.  You can
-specify the path to match relative to the namespace of the current
-module as an argument to C<Path>.  For example C<Path('list')> in
-C<lib/MyApp/Controller/Books.pm> would match on the URL
-C<http://localhost:3000/books/list> but C<Path('/list')> would
-match on C<http://localhost:3000/list>.
-
-=item *
-
-Automatic "chaining" of actions by the dispatcher is a powerful
-feature that allows multiple methods to handle a single URL.  See
-L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained>
-for more information on chained actions.
-
-=item *
-
-There are five types of build-in 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
@@ -582,6 +546,7 @@ 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
@@ -619,25 +584,38 @@ running) and restart it:
 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.  Note that you can quickly sync an Ubuntu
-system with the following command:
+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 ntp.ubuntu.com
+    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 (below the closing </table> tag):
 
     <p>
       <a href="[% c.uri_for('/login') %]">Login</a>
-      <a href="[% c.uri_for('form_create') %]">Create</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
@@ -659,12 +637,16 @@ from cleartext passwords to SHA-1 password hashes.
 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
 between the browser and your application, consider using SSL/TLS, made
-easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
+easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.  You should
+also consider adding a "salt" mechanism to your hashed passwords to
+mitigate the risk of a "rainbow table" crack against your passwords (see
+L<Catalyst::Authentication::Credential::Password|Catalyst::Authentication::Credential::Password>
+for more information on using a salt value).
 
 
 =head2 Get a SHA-1 Hash for the Password
@@ -677,12 +659,6 @@ dirty" way to do this:
 
     $ perl -MDigest::SHA -e 'print Digest::SHA::sha1_hex("mypass"), "\n"'
     e727d1464ae12436e899a726da5b2f11d8381b26
-    $
-
-B<Note:> If you are following along in Ubuntu, you will need to install
-C<Digest::SHA> with the following command to run the example code above:
-
-    sudo aptitude install libdigest-sha-perl
 
 B<Note:> You should probably modify this code for production use to
 not read the password from the command line.  By having the script
@@ -713,12 +689,13 @@ 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>
+=head2 Enable SHA-1 Hash Passwords in Catalyst::Plugin::Authentication::Store::DBIC
 
 Edit C<myapp.conf> and update it to match (the C<password_type> and
 C<password_hash_type> are new, everything else is the same):
 
+    # rename this file to MyApp.yml and put a : in front of "name" if
+    # you want to use yaml like in old versions of Catalyst
     name MyApp
     <authentication>
         default_realm dbic
@@ -743,9 +720,9 @@ C<password_hash_type> are new, everything else is the same):
                     # Use DBIC to retrieve username, password & role information
                     class DBIx::Class
                     # This is the model object created by Catalyst::Model::DBIC
-                    # from your schema (you created 'MyApp::Schema::User' but as
-                    # the Catalyst startup debug messages show, it was loaded as
-                    # 'MyApp::Model::DB::Users').
+                    # from your schema (you created 'MyApp::Schema::Result::User'
+                    # but as the Catalyst startup debug messages show, it was
+                    # loaded as 'MyApp::Model::DB::Users').
                     # NOTE: Omit 'MyApp::Model' here just as you would when using
                     # '$c->model("DB::Users)'
                     user_class DB::Users
@@ -754,6 +731,7 @@ C<password_hash_type> are new, everything else is the same):
         </realms>
     </authentication>
 
+
 =head2 Try Out the Hashed Passwords
 
 Press C<Ctrl-C> to kill the previous server instance (if it's still
@@ -762,21 +740,21 @@ 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>).
 
 
 =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 (everything after the model search line of code
@@ -788,37 +766,42 @@ has changed):
 
     =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('DB::Books')->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
-        $c->response->redirect($c->uri_for('/books/list'));
+        $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 || c.flash.status_msg %]</span>
-    <span class="error">[% error_msg %]</span>
-    [% content %]
-    </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 -->
+    ...
 
-    <div id="footer">[% PROCESS site/footer %]</div>
+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
 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
@@ -832,15 +815,15 @@ after that first time (unless you reset it).  Please refer to
 L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional
 information.
 
+
 =head2 Switch To Flash-To-Stash
 
-Although the a use of flash above is certainly an improvement over the
-C<status_msg> we employed in Part 4 of the tutorial, the C<status_msg
-|| c.flash.status_msg> statement is a little ugly. A nice
+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 code controller
+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
+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:
 
@@ -859,8 +842,8 @@ 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/lib/site/layout> and change the C<status_msg> line
-to look like the following:
+Then edit C<root/src/wrapper.tt2> and change the C<status_msg> line
+to match the following:
 
     <span class="message">[% status_msg %]</span>
 
@@ -877,7 +860,7 @@ 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-2008, Kennedy Clark, under Creative Commons License
 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).