subversion repository as per the instructions in
L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
+
=head1 BASIC AUTHENTICATION
This section explores how to add authentication logic to a Catalyst
=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.conf> and automatically load this information
-into C<MyApp-E<gt>config> using the
+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.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
-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.
+As discussed in Part 3 of the tutorial, Catalyst has recently
+switched from a default config file format of YAML to
+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>
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
$ 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.
+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 update the
-definition of C<sub index> 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
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
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>,
+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
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
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 an Ubuntu system with
+the following command:
sudo ntpdate ntp.ubuntu.com
<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
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.
=head2 Get a SHA-1 Hash for the Password
$ 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>).
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
+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> part of the
-tutorial.
+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
=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/src/wrapper.tt2> and update the TT code to pull from
=head2 Try Out Flash
-Restart the development server and 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
+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
"Book deleted" status message across the redirect.
B<NOTE:> While C<flash> will save information across multiple requests,
=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
+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
+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: