Social implementations of three-legged oAuth
I was implementing some functionality utilizing the Twitter API and found the documentation to be extremely lacking/unclear.
Given that the oAuth protocol seems more complicated than it actually is, I thought I'd document some extra explanation to accompany Twitter's sign in implementation docs.
3 Legged oAuth
Twitter's explanation of 3 Legged oAuth is rubbish. Full stop.
Let me briefly try and explain what is is because there seem to be very few resources that explain it well.
It is called three legged oAuth because there are three participants: the User, Website, and Service.
That said the process can also be considered as being made up of three stages as follows:
Leg 1
- User wants to provide Website with data from Service
- Website tells Service that it wants said data.
Leg 2
- Website sends User to Service
- User authorizes data request.
- Service sends User back to Website
Leg 3
- Website requests access token from Service
Why?
Website can access User's data from Service without knowing User's username and password for Service
Overview
Twitter's implementation of those three legs is as follows:
request_token
The request_token step of the oAuth process is essentially telling Twitter who you are and what you want.
You as the consumer pass in your consumer_key
and consumer_key_secret
- this indicates who you are.
Twitter then knows what permission you want: read only; read and write; or read, write, and direct message access - you have set these in your apps settings.
You can also pass in an oauth_callback
header - this tells Twitter where to send your user once authorization is complete. If you don't pass this header Twitter will redirect users to the callback URL set in your settings. I find it worthwhile explicitly setting this on each request such that you can swap out different callback URLs for development.
I encountered an issue with the oauth_callback
header which was resulting in me receiving the error message 'Failed to validate oauth signature and token'. If I did not pass in an oauth_callback
header I would receive a token without issue.
My problem was with my oauth signature. It required that my oauth_callback
was encoded twice.
As is typical, it is significantly easier to find out about an issue when you know what that issue. A post-fix search presented me with this stack overflow answer which extremely succinctly explains why it needs to be encoded twice.
Another important consideration when creating your oAuth signature is to order your parameters alphabetically when you are combining them to build a signature.
oauth/authenticate - oauth/authorize
Your user should be sent to one of these endpoints passing the request_token
returned by the previous step. Here they will authorize your application to access/not access their data.
The former endpoint will automatically redirect an already authorized user to the oauth_callback
url specified in the previous step. oauth/authorize
requires authorization each time.
On completion the user is redirected to your oauth_callback
. An oauth_token
and oauth_verifier
parameter are passed back if the user has authorized your app.
oauth/access_token
Finally, you POST the oauth_verifier
to this endpoint and an oauth_token
/oauth_token_secret
will be returned. If you write these down ;) or store them in a database, you can access the users protected data without the above process until the tokens expire.
Reliance on Libraries
On a previous project I utilized abraham's PHP twitter library. I remember having no issues with it, but it was very much the case that I was unaware of its internals.
This time I figured I would read up on oAuth and make sure i knew exactly what my code was doing. Whilst browsing i came across j7mbo's twitter wrapper which is a very barebones PHP wrapper for the Twitter API. The beauty of it is that:
- It works
- It is simple, easy to understand, and only 300 lines long
There is no point uneccesarily reinventing the wheel so I took this wrapper and extended it to my needs.
Because of the simplicity of the code it was relatively painless for me to debug the double encoding issue with oauth_callback
that I outlined above.
Comparison with the Facebook API
If you are implementing functionality that utilizes the Twitter API, the chances are that you are implementing something more generically social. I was - I also needed to work with the Facebook API.
There are a few similarities between the APIs:
- They both utilize oAuth
- The both have rubbish documentation
That said I found myself enjoying working with the Facebook API to a greater extent.. (is that weird?).
Firstly Facebook provides an official PHP SDK - in my mind I can be relatively confident that it is well tested, and works. It is a significantly more complex wrapper than the Twitter wrapper mentioned above but one can assume that a company like Facebook would religiously test their SDKs.
In the same way that when testing your own products you assume that external APIs work (or you don't use them), I am happy to assume that the SDK works.
What I like about this wrapper is the helper functions. They provide a FacebookRedirectLoginHelper
class which means that to get the login URL to which I redirect the user (to get their authorization), I simply need to call the getLoginUrl
method.
On top of that, responses are wrapped up in an object orientated interface so I can get my access token by calling $session->getToken();
and can get properties of the graph objects contained within my response using $graphObject->getProperty('email');
That wasn't so oAuthful..?
So there you have it.. a basic overview of oAuth and its utilization by two social powerhouses.
It has now gotten to a point where I have implemented these APIs so many times that it is somewhat second nature.
The beauty of the oAuth protocol is that once you have a grasp on what it is, and how it works it is clear to see why it is so powerful.
If you want to learn more check out the oAuth website.
Now I'm going to ponder implementing oAuth security into one of my publicly available APIs that really has zero use for it ;)