[Catalyst-commits] r8915 -
trunk/examples/CatalystAdvent/root/2008/pen
jshirley at dev.catalyst.perl.org
jshirley at dev.catalyst.perl.org
Thu Dec 18 19:53:34 GMT 2008
Author: jshirley
Date: 2008-12-18 19:53:34 +0000 (Thu, 18 Dec 2008)
New Revision: 8915
Added:
trunk/examples/CatalystAdvent/root/2008/pen/progressive_auth.pod
Log:
Initial draft of progressive auth article.
Added: trunk/examples/CatalystAdvent/root/2008/pen/progressive_auth.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2008/pen/progressive_auth.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2008/pen/progressive_auth.pod 2008-12-18 19:53:34 UTC (rev 8915)
@@ -0,0 +1,237 @@
+=head1 Progressive Authentication with Catalyst (Using OpenID)
+
+I<Note: > This article has an accompanying example application that can be
+downloaded and ran, please see the L</"Try it and see"> section for more
+information.
+
+=head2 A brief overview of authentication
+
+Often times, as applications (especially any application with social aspects)
+grow and evolve there becomes a necessity for "other" forms of authentication.
+Most typically, this is in the form off a temporary password that is emailed
+to a user. Many applications actually change the users password to some
+generated password, which means that the previous password is invalid. Other
+applications just email out in plain text.
+
+Both of these solutions are wrong. (Now I'm just going to leave it at that)
+
+This situation directly inspired the development of the Progressive Realm
+(L<http://search.cpan.org/~jayk/Catalyst-Plugin-Authentication-0.100091/lib/Catalyst/Authentication/Realm/Progressive.pm>).
+While we won't cover proper handling of temporary passwords, since that is a
+more involved topic and much larger article (to be discussed at a later date,
+but I provide a brief note at the bottom of this article).
+
+Instead, I will cover how to query multiple realms to do authentications in a
+very simple setting.
+
+=head2 OpenID and Local Authentication
+
+The simple example is centered around handling OpenID. OpenID, by itself, is
+very simple. However there are some nits that eventually end up in ugly and
+unmaintainable code, unless you have the foresight to handle it. This
+foresight usually comes from writing the above mentioned ugly code. The
+subsequent solution that isn't ugly tends to look very similar to the
+Progressive realm, hence its creation.
+
+First off, to handle OpenID and local authentication is no difficult matter.
+In fact, some would call it trivial. To get it running, it requires nothing
+more than the Authentication plugin, the OpenID credential and a session
+plugin. The main issue to overcome with OpenID is that while you can
+associate the authenticating URL to an account, sometimes users also have a
+password on the site. It can be difficult to handle the authentication for
+both, and often times the login controller becomes ugly. There is far too
+much logic there, and it doesn't really do much other than determine which
+realm the user is in. The Progressive realm is the ready made solution!
+
+(As an aside, a very cool usage for this is to have an application that
+progressively reveals features to users based on their authenticating realm)
+
+To configure the realms, you first have to load the authentication plugin.
+Also, you need a session store because what good is authenticating users if
+their authentication only lasts for one request. To do this, we just add the
+following plugins to MyApp.pm:
+
+ use Catalyst qw/
+ ConfigLoader Static::Simple
+ Authentication Session Session::Store::FastMmap Session::State::Cookie
+ /;
+
+=head2 The Simple Case: Local Authentication
+
+Now, the Authentication and Session plugins are loaded and it is time to
+configure Authentication. More Authentication configurations just use the
+Password credential, and the configuration looks something like this:
+
+ __PACKAGE__->config(
+ name => 'MyApp',
+ 'Plugin::Authentication' => {
+ default_realm => 'local',
+ realms => {
+ 'local' => {
+ credential => {
+ class => 'Password',
+ password_field => 'password',
+ password_type => 'clear'
+ },
+ # A more typical store is the DBIC store
+ store => {
+ class => 'Minimal',
+ users => { ... }
+ }
+ },
+ }
+ }
+ );
+
+Now, a simple call to authenticate in your controller will check the password
+against the store and either succeed or fail. The code here is just very
+simple:
+
+ if ( $c->authenticate({ username => $username, password => $password }) ) {
+ $c->log->info("We did it!");
+ } else {
+ $c->log->info("User failed it!");
+ }
+
+We don't specify a realm, because the default realm is C<local> and that's it.
+
+=head2 Adding OpenID
+
+Up next is adding in OpenID. It's a simple adjustment to the configuration to
+add the credential in:
+
+ __PACKAGE__->config(
+ name => 'MyApp',
+ 'Plugin::Authentication' => {
+ default_realm => 'local',
+ realms => {
+ 'openid' => {
+ credential => {
+ class => 'OpenID'
+ },
+ store => {
+ class => 'Null',
+ }
+ },
+ 'local' => {
+ credential => {
+ class => 'Password',
+ password_field => 'password',
+ password_type => 'clear'
+ },
+ store => {
+ class => 'Minimal',
+ users => { ... }
+ }
+ },
+ }
+ }
+ );
+
+Now, we can change the authentication method a bit. Before the progressive
+realm, the code starts to look like this:
+
+ my $data = {};
+ my $realm;
+
+ if ( $c->req->params->{openid_identifier} ) {
+ $data->{openid_identifier} = $c->req->params->{openid_identifier};
+ } else {
+ $data->{username} = $c->req->params->{username};
+ $data->{password} = $c->req->params->{password};
+ }
+ if ( $c->authenticate( $data, $realm ) ) {
+ $c->log->info("We did it!");
+ } else {
+ $c->log->info("User failed it!");
+ }
+
+That's not bad code, but it's a lot more than just using a single realm. The
+problem is when you continue to add realms and other mechanisms for users to
+authenticate (webservices, temporary passwords) it grows and becomes more
+unmaintainable. Eventually, it ends up on The Daily WTF and people refer to
+it with hand waves and resignations.
+
+=head2 Now, with Progressive Realms
+
+The first step is to simply configure a default realm that uses the
+Progressive realm class, and then list what realms are legitimate to try.
+
+ __PACKAGE__->config(
+ name => 'MyApp',
+ 'Plugin::Authentication' => {
+ default_realm => 'progressive',
+ realms => {
+ progressive => {
+ class => 'Progressive',
+ realms => [ 'openid', 'local' ],
+ },
+ 'openid' => {
+ credential => {
+ class => 'OpenID'
+ },
+ store => {
+ class => 'Null',
+ }
+ },
+ 'local' => {
+ credential => {
+ class => 'Password',
+ password_field => 'password',
+ password_type => 'clear'
+ },
+ store => {
+ class => 'Minimal',
+ users => { ... }
+ }
+ },
+ }
+ }
+ );
+
+With the configuration finished, it's time to modify the authenticate call to
+remove the specific realm (or, if C<progressive> isn't your default realm to
+set that explicitly).
+
+Also, it is a good idea to filter exactly what you are putting into the
+authenticate call. Then it is a simple "whitelist" check and you pass in the
+entire stash. The code for authenticating now looks like this:
+
+ my %data = map { $_ => $c->req->params->{$_} }
+ # Filter out parameters not defined
+ grep { defined $c->req->params->{$_} }
+ # List of parameters potentially used:
+ qw/openid_identifier username password/;
+ if ( $c->authenticate({ %data }) ) {
+ $c->log->info("We did it!");
+ } else {
+ $c->log->info("User failed it!");
+ }
+
+When new realms are added, simply add the parameter keys to the list of
+parameters and things should transparently work. The order of C<realms> in
+the Progressive configuration determines the order the subordinate realms are
+called, and whichever one matches first returns.
+
+=head2 Try it and see
+
+This advent entry is accompanied by a fully working example application that
+demonstrates the principles. It is available as a standalone tarball at
+L<http://www.coldhardcode.com/examples/catalyst/ProgressiveAuth-0.01.tar.gz>
+or if you prefer an svn checkout:
+
+ svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/ProgressiveAuth/
+
+=head1 Author
+
+Jay Shirley, the IT Director at L<http://www.nasaproracing.com> and Co-Founder
+of L<http://www.coldhardcode.com>. He is a die-hard Catalyst user, evangelist
+and perl hacker.
+
+=head2 Appendix A: A note on temporary passwords
+
+As a follow-up, the proper solution for temporary passwords is to have
+multiple realms for authentication, one for temporary passwords and one for
+legitimate passwords, if the user authenticates in the temporary realm then
+you force a password change. If the user authenticates via the normal realm,
+nothing changes.
More information about the Catalyst-commits
mailing list