Progressive Authentication with Catalyst (Using OpenID)

Note: This article has an accompanying example application that can be
downloaded and run. Please see the &quot;Try it and see&quot; section for more
information.

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 of a temporary password that is emailed
to a user. Many applications actually change the user's password to some
generated password, making the previous password invalid. Other
applications just email the forgotten password in plain text.

Both of these solutions are wrong. (Now I'm just going to leave it at that.)

Instead, I will cover how to query multiple realms to do authentications in a
very simple setting.

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 result 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 an almost
trivial matter. 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:

The Simple Case: Local Authentication

Now, the Authentication and Session plugins are loaded and it is time to
configure Authentication. Most Authentication configurations just use the
Password credential, and the configuration looks something like this:

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 (web services, 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.

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.

With the configuration finished, it's time to modify the authenticate call to
remove the specific realm (or, if 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:

When new realms are added, simply add the parameter keys to the list of
parameters and things should work transparently. The order of realms in
the Progressive configuration determines the order the subordinate realms are
called, and whichever one matches first returns.

Author

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.