Have you recently used an API for accessing Twitter, Facebook or Google? If so, you probably authenticated with OAuth. Instead of using their own authentication schemes, most new services choose to implement OAuth2, the latest revision of the OAuth protocol. It gives your users a secure way to talk to your service, but more importantly, allows users to safely authorize access to their data from third-party services without giving them their credentials. Let's build a sample server implemtation in Ruby and see how it all works.
Since there are a few moving pieces, take a look at this graph of the typical flow of authentication:
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
We start off asking the Resource owner to authorize our request, something like the ability to post tweets on their behalf. The user would then accept our request by clicking a link that would redirect them to Twitter and allow them to enter their credentials. Once Twitter confirms the credentials, it redirects the user back to our service including an access token. We can then use this access token to take the action we had previous asked the user about. The advantages here are that the user never gave us their credentials directly, only granted us a specific scope in which to operate, and still has the ability to revoke our access without changing their credentials or affecting other authorized services. Ok that was a lot of explaining. Let's take a look at some code.
So you want to be able to authorize requests just like twitter? Well, we can
start out by generating a new rails app and creating an OAuthController
.
While it's not the best design, to keep things simple we're going to do most of
the work right in the controller. We can start off by allowing users to
register their application with an application
action.
First we'll need to create an Application
class to hold on to the
client_id
.
class Application < ActiveRecord::Base
has_many :authorizations
before_save :generate_client_id
def generate_client_id
self.client_id = SecureRandom.hex(32)
end
end
Now we can just create one of these in the matching controller action (we'll add routes for all of these at the end).
def application
respond_with Application.create
end
Ok now were on to the important part. We need to verify the application and
send the access_token
back to the requesting application. But remember this
isn't an API call, we have to redirect back.
So first let's create another model to hold the access_token
we generate.
class Authorization < ActiveRecord::Base
belongs_to :application
before_save :generate_access_token
def generate_access_token
self.access_token = SecureRandom.hex(32)
end
end
Now we just need to redirect back to the original url with the access token.
def authorization
application = Application.where(client_id: params[:client_id]).first
authorization = application.authorizations.create
redirect_to "#{params[:request_uri]}?access_token=#{authorization.access_token}"
end
Now for the final touches, wiring up the routes to controller actions.
post '/applications' => 'oauth#application'
get '/authorization' => 'oauth#authorization'
So let's review how we can use these routes we just made. First we register
our application by posting to /applications
. In the response we can find the
client_id
parameter used to generate an access_token
. To authorize a user
we need to redirect them to /authorization?client_id=...
where the
application will allow for its own authorization step and then redirect back to
the request_uri
along with the newly generated access_token
.
But what about authorizing requests from a client other than a broswer? All we
need to do is add another way to generate an access_token
. Typically this
means using HTTP Basic Authentication with the credentials of the user. While
this doesn't give us the advantage of keeping our credentials from the client
application, at least they are only used once after which we can revoke access
independently.
Keep in mind we didn't cover many peices of OAuth2 that would be present in a typical production implementation. To implement a full provider we would need more in-depth attributes for applications, knowledge of scope parameters as they related to our application, more robust redirection allowing for multiple query parameters, and better security by validating a client secret and supporting refresh tokens. For a great example check out GitHub's implementation of OAuth2.
In the next episode we'll cover how to use OAuth2 from an iOS app.