OAuth and Catalyst
I’ve been working on and off on a hobby Catalyst application for a while now and one of the things I was not happy with was the authentication system, which felt like a bit of a hack.
I was able to dedicate a bit of time into understanding Catalyst’s authentication quirks at the weekend and really nail my approach. I couldn’t find the right documentation to explain exactly what was required, so here’s my attempt, to help anyone else who might be trying something similar.
Let’s say you have a web application, and you want to allow users to log in to your application, then you might want to defer the registration and authentication to another service, like Facebook or Google, and user their OAuth login systems.
This allows a user to login to the third party service and authorise your web application access to their account. This gives you access to some details of their account and also verifies them as a user that can also log in to your web application.
You can read Wikipedia for a more thorough explanation of how it all works.
Catalyst is a well known Perl MVC web framework, akin to Ruby’s Ruby on Rails or Python’s Django.
Catalyst is very flexible and extensible and already has good support for authentication and authorisation.
As mentioned, Catalyst already has good authentication and authorisation support through Catalyst::Plugin::Authentication.
You can read the linked documentation to see how it all works, but it’s pretty straightforward. Oftentimes you’ll use it with a backend store like Catalyst::Authentication::Store::DBIx::Class and all will be good with the world.
If you want to defer your authentication to a third party OAuth provider, there already exist a number of modules to do this for you. For example, I have used Catalyst::Authentication::Credential::Facebook::OAuth2 and it works really nicely. I also wanted to provide login through Strava and started to write my own to authenticate code using Strava’s API. Things get a bit tricky at this point. What if you want to customise the behaviour of the authentication realm (plugin) or do more than merely authenticate a user?
In my case I had two issues:
- I need to associate a user with only one account in my application, if they login across the different providers but are really the same user (i.e. have the same email address).
- I want to grab more information from the authentication provider than merely the authentication token because there’s other useful stuff there.
This is where I recently learnt about a useful module called Catalyst::Authentication::Realm::Adaptor that allows you to customise the behaviour of the authentication plugins. This is exactly what I had been looking for.
It gives you two places to hook into and alter the behaviour. Now, most people might not need any of this but it is especially useful with OAuth authentication.
One really simple thing you can do with this is rename the column the authentication
realm uses to lookup and store your access code. For example, the default field used
by the Facebook::OAuth2 realm is
token. This is fine if you’re only setting up
login with Facebook but if you want to support multiple OAuth providers, may not
be as appropriate because you’ll have to add multiple columns to your user table,
as specified by the author of the authentication module.
Putting it all together
So in my application, I want to provide login via Facebook and Strava. And I want to be able to link users from Facebook and Strava, if they login to my application using the same account (email address). You might say that this is placing too much trust in the provider but I say this is what they are here for. I also want to retrieve and store more information about the users from their profiles on the third party services for use in our application.
We can do all of this with the modules mentioned above, some minimal controller actions and resultset/row methods and a bit of config to tie it all together.
First the configuration for our app:
Our login actions:
And, finally, our auto_create and auto_update methods:
So, mostly with the help of Catalyst::Authentication::Realm::Adaptor, we can get the exact customised login behaviour we need without hardcoding any hacks into the core authentication code.
It’s probably worth factoring out the code to conncet to the APIs, if you think you’ll need to reuse this in your application but I haven’t done this yet.
If you’d like to use Strava for login, Catalyst::Authentication::Credential::Strava should be heading to CPAN soon.