You’re building an API, which means you want other people building client applications that use API, and end user accessing your service through those client applications.
Rack::OAuth2::Server lets you open up your API and manage end-user authentication and client application authorization using OAuth 2.0. If you already know you want to use OAuth 2.0, skip to the next section and read how to use Rack::OAuth2::Server. If you’re not sure, let me tell you why I chose OAuth 2.0.
Passwords, OAuth, One and Two
What you definitely don’t want to do is have your users sharing their username and password with those client applications. It’s just bad karma.
For one, the client application might lose their username/password. And by “lose” I mean, “someone not so scrupulous may have found them”. It happens. Some applications are put together in a weekend, others are tacked-on as minor features to a larger product, so don’t expect them all to have top of the line security. And even if they tried, client applications can’t salt and hash these passwords, they’re forced to store them as clear text. Hope I don’t have to tell you how bad that is.
What we’re going to do instead is use access tokens. An access token is tied up to one end user account and one client application. Losing an access token only compromises one end user, or one client application, both easier to recover from. And if you need to, you can revoke all the access token for a rogue application, without having to reset everyone’s password or access.
Another benefit, end users themselves can revoke individual access tokens, so they get control over who’s accessing their account. That’s much harder to do if they handed over their password. Easier access control means they’re more likely to try out new client applications, a benefit to both end users and 3rd party developers.
Last but not least, you can offer end users more granular access control. When you hand over your username/password to another application, that application can do anything it wishes to with your account. It can read stuff, post stuff, change your profile photo, lock you out of your account, even delete your account. In some cases it can even make charges against your credit card.
Access tokens can be limited in scope. You can authorize access token to only read data, read and write but not make charges, and certainly you won’t allow an access token to lock someone out of their account.
There are several standards to pick from, allow me to ignore the more enterprisey ones, you won’t enjoy using them either. In the user/developer-friendly camp we find two of these, OAuth 1.0 and OAuth 2.0.
OAuth 1.0 is the more popular one. Twitter uses it, so we know it works well, but it’s too complex for what it does. You can’t play with it from the browser’s address bar or curl. It’s more demanding and asks that you use a client library properly wrap up all HTTP request.
OAuth 2.0 is the younger, not yet finalized, sibling. Facebook uses it for their graph API, so we know it also works well. OAuth 2.0 is not backwards compatible, and it’s simplified usage model means you can play with it from your browser, curl, or your favorite HTTP library. No special tooling required. It’s also easy to use for authorizing client-side JavaScript.
So let’s talk about Rack::OAuth2::Server and how we can use it to implement OAuth 2.0 authorization, and I’m gong to work backwards because it’s easier and more interesting to explain that way.
Authenticating Requests
Let’s start by tackling the more common flow of control, assume we already have an access token we want to use, and let’s make an authenticated request.
This part is dead simple. The client application sends an HTTP request using the Authorization header to send it’s access token:
$ curl -i https://api.example.com/free/beer -H "Authorization: OAuth 4c7c6276b3376858"
BTW access tokens are generally much longer than that, I’ll use short ones for these examples. Curl tip of the day: you can put that token in a curl config file, for example:
$ echo ex header = "Authorization: OAuth 4c7c6276b3376858" $ curl -i -K ex https://api.example.com/free/beer
Now, if you rename that file .curlrc an move it to your home directory, curl will automatically load it. Don’t do that with production access tokens, or you’ll end up sending them to whatever server you happen to curl. It’s a good trick for development/test environments.
On the client side, you could do something like this:
$(document).ajaxSend(function(e, xhr, settings) {
xhr.setRequestHeader("Authorization", "OAuth " + document.access_token);
})Authorization header is recommended, if you can’t use that, two other options are to pass the access token in the query parameter oauth_token, or when submitting an HTML form, form field with the same name.
On the server side, Rack::OAuth2::Server intercepts requests that carry an OAuth authorization header, retrieves the access token from the database, and checks its validity (specifically, that it was not revoked). On failed attempts to authenticate, it responds with 401 (Unauthorized).
If the request carries a valid token, it sets the request headers oauth.access_token and oauth.identity. The later is whatever identity value you provided when authorizing the access token, we’ll talk about that soon, and usually it would be user ID or account ID. Here’s an helper method that resolves the oauth.identity header back to a User object:
def set_current_user @current_user = User.find(oauth.identity) if oauth.authenticated? end
The oauth method is available to Rails and Sinatra/Padrino apps, it’s a thin wrapper for accessing the request/response headers that are part of the Rack::OAuth2::Server protocol. For example, oauth.identity is the same as request["oauth.identity"].
Rack::OAuth2::Server rejects improperly authenticated requests, but does not reject unauthenticated requests. Your application can decide whether to block access to unauthenticated requests, or simple handle them differently, for example, return a mixture of public and private data, or offer different rate limits.
To force authenticated requests, you can check the value of oauth.authenticated? and respond with 401. It’s even better if you return a proper WWW-Authenticate header. You can ask Rack::OAuth2::Server to do that for you by setting the response header oauth.no_access and status code 401. Or use the oauth_required method to declare a filter.
Likewise, you can restrict access by scope. Let’s say only clients with access scope “math” can perform calculation. You can check the scope granted to the access token by calling oauth.scope, and “math” is not one of the names, respond with status code 403 (Forbidden). Again a proper WWW-Authenticate header with the required scope is better, and you can request that by setting the response header oauth.no_scope. Or use oauth_required with the :scope option.
Here’s a Rails controller example using these techniques:
class MyController < ApplicationController oauth_required :only=>:private oauth_required :only=>:calc, :scope=>"math" # Authenticated/un-authenticated get different responses. def public if oauth.authenticated? render :action=>"more-details" else render :action=>"less-details" end end # Must authenticate to retrieve this. def private render end # Must authenticate with scope math to do this. def calc render :text=>"2+2=4" end protected def current_user @current_user = User.find(oauth.identity) if oauth.authenticated? end end
And here’s what it would look like in Sinatra/Padrino:
before do @current_user = User.find(oauth.identity) if oauth.authenticated? end oauth_required "/private" oauth_required "/calc", :scope=>"math" # Authenticated/un-authenticated get different responses. get "/public" do if oauth.authenticated? render "more-details" else render "less-details" end end # Must authenticate to retrieve this. get "/private" do render "secrets" end # Must authenticate with scope math to do this. get "/calc" do render "2 + 2 = 4" end
That’s all you need to know to secure your API using OAuth 2.0. Now let’s see how client applications get hold of the access token.
Getting Your Access Token
So how does one get their bling … err access token? There’s an endpoint for that, by convention we use /oauth/access_token. Rack::OAuth2::Server supports three ways to obtain an access token. You can use username/password, which works for some applications or if you don’t have a Web UI. You can use authorization codes, which is the more conventional way, and requires the end-user to authorize access from their Web browser — without sharing username or password. And you can use a shortcut method, which works great for client-side JavaScript applications.
Let’s start with username/password, since it’s simpler to explain. The client application needs to know four things: its client ID, its client secret, the username and the password. And how to make an HTTP POST request:
$ curl -i https://api.example.com/oauth/access_token \ -F "scope=read write" \ -F client_id=4313e295c5b0 \ -F client_secret=cc6276b73653831 \ -F grant_type=password \ -F username=assaf@labnotes.org -F password=not-telling
What you get back is a JSON document (told you OAuth 2.0 is simple) that contains the access token.
On the server side, Rack::OAuth2::Server will ask your application to authenticate by using an authenticator block, similar to the one you’d use for HTTP Basic authentication in Rack or Rails. For example:
Rails::Initializer.run do |config| config.oauth.database = Mongo::Connection.new["db1"] config.oauth.scopes = %w{read write} config.oauth.authenticator = lambda do |username, password| user = User.find(username) user if user && user.authenticated?(password) end . . . end
If you don’t want to support username/password authentication, don’t supply an authenticator.
The other way to get an access token is to have an authorization code ready. You get the single-use authorization code during the authorization step, which we’ll talk about soon. For now, assume you’ve got one, so let’s exchange it for an access token:
$ curl -i https://api.example.com/oauth/access_token \ -F "scope=read write" \ -F client_id=4313e295c5b0 \ -F client_secret=cc6276b73653831 \ -F redirect_uri=https://awesome.app/oauth/callback \ -F grant_type=authorization_code \ -F code=b14a254b25ee97
Again, you’re exchanging form parameters for a JSON document containing the access token. On the server side, Rack::OAuth2::Server intercepts requests to /oauth/access_token and takes care of the details.
Now let’s see how your application can get an authorization code.
Authorization
Now we get to do some UI stuff. We start with an end user that has their browser open to the client application’s site. They need a new access token, and so the client application redirects them to the authorization page. By convention we use the path /oauth/authorize.
The redirect includes several query parameters: the client ID, the client secret, the redirect URL, the desired access scope, and the desired response type. For example:
def cool_stuff if user.access_token . . . # coolness else # Don't have their access token authorize = URI.parse("https://api.example.com/oauth/authorize") authorize.query = { :client_id=>"4313e295c5b0", :client_secret=>"cc6276b73653831", :redirect_uri=>oauth_callback_url, :response_type=>"code", :scope=>"read write" }.to_query redirect_to authorize.to_s end end def callback # Woot! code = params["code"] uri = URI.parse("https://api.example.com/oauth/access_token") params = {:client_id=>"4313e295c5b0", :client_secret=>"cc6276b73653831", :redirect_uri=>oauth_callback_url, :code=>code, :grant_type=>"authorization_code", :scope=>"read write" } response = Net::HTTP.post_form(uri, code) json = JSON.parse(response.body) user.access_token = json["access_token"] end
So the client application redirected the end user to your site. You need to authenticate them, and ask them to authorize the client application. Here Rack::OAuth2::Server intercepts the requests and does the following. In the original request, choke full of query parameters, it’s going to validate the request, and bounce it back if anything is wrong or looks fishy.
Otherwise, it passes control to your application, and sets the request headers oauth.authorization. From that, you can glean a lot of information, like the requested scope (oauth.scope), the client name (oauth.client.display_name) and so forth.
It’s possible the end user is not logged into your site, so you’re going to have to log them in, a flow that can take one or more pages. You’ll need to keep track of the authorization handle, by passing it around as a query parameter, that way, when you redirect back to /oauth/authorize with the query parameter authorization, Rack::OAuth2::Server knows what to do with it.
So now we have an authorization view that looks something like this:
<h2>The application <% link_to h(oauth.client.display_name), oauth.client.link %> is requesting to <%= oauth.scope.to_sentence %> your account.</h2> <form action="/oauth/grant"> <button>Grant</button> <input type="hidden" name="authorization" value="<%= oauth.authorization %>"> </form> <form action="/oauth/deny"> <button>Deny</button> <input type="hidden" name="authorization" value="<%= oauth.authorization %>"> </form>
When the end user grants the request, we’re going to tell Rack::OAuth2::Server about it by setting the response headers oauth.authorization and oauth.identity and returning the status code 200. In oauth.identity set whatever value you need during authenticated access, typically user ID or account ID, or anything that can be saved as a string.
If the user denies the request, we’re going to tell Rack::OAuth2::Server about that as well by setting the response header oauth.authorization and returning the status code 401 (Unauthorized). Again, there are nice helper methods for doing all of that, let’s see an example in Rails:
class OauthController < ApplicationController def authorize if current_user render :action=>"authorize" else redirect_to :action=>"login", :authorization=>oauth.authorization end end def grant head oauth.grant!(params[:authorization], current_user.id) end def deny head oauth.deny!(params[:authorization]) end end
And using Sinatra/Padrino:
get "/oauth/authorize" do if current_user render "oauth/authorize" else redirect "/oauth/login?authorization=#{oauth.authorization}" end end post "/oauth/grant" do oauth.grant! params[:authorization], "Superman" end post "/oauth/deny" do oauth.deny! params[:authorization] end
If you’re using JavaScript, you can request an access token directly without having to receive an authorization code first. As above, you have to redirect the user, but with the query parameter response_type set to “token”. The response will include the access_token within the fragment identifier:
var access_token = document.location.hash.match(/[&#]token=(.*?)(&|$)/)[1];
Client Management
As you figured by now, clients have to be registered before they can get access token. Each client gets a unique identifier and secret, which they use to authenticate themselves. Since both ID and secret are passed around in plain sight, clients can opt for more security by registering their redirect URL. Requests for a client that registered its redirect URL can only ever redirect back to that site.
Separately, you’ll want end users to authorize the client application based on as much details as they can muster. I’d recommend using the client’s display name, site URL and image URL, and showing those in the authorization view.
For example:
> dbl_rainbow = Rack::OAuth2::Server::Client.create(:display_name=>"DoubleRainbow", :link=>"http://rainbow.to/", :image_url=>"http://farm5.static.flickr.com/4122/4890273282_58f7c345f4.jpg", :redirect_uri=>"http://rainbow.to/oauth/callback") > puts "Your client identifier: #{client.id}" > puts "Your client secret: #{client.secret}"
Rack::OAuth2::Server do not include any UI for managing clients, access tokens and such. Contributions more than welcome.
Final Words
Rack::OAuth2::Server does all the heavy lifting of handling the OAuth 2.0 protocol, and there’s quite a lot of that. I tried to keep the internals as straightforward as possible, for maintenance sake, but also to make it easy to port to other languages. If you’re thinking “this would be a cool node.js project”, let me know, I’d like to help.
For the same reason, I kept the protocol between the Rack module and application as simple as possible. The Rack module passes information to the application through request headers, and looks for suitable response headers and matching status codes, indicating when it should take control and what it should do. Having the protocol well defined makes it easy to integrate in different environments that support Rack. The Rack and Sinatra/Padrino wrappers are paper thin and took no time to write.
I chose to use MongoDB for storing registered clients, authorization state (authorization requests and access grants) and access tokens. Everything else, by which I mean user accounts, roles, etc, are not part of OAuth 2.0 and handled by the application. MongoDB is awesome, and if you don’t have the time to set it up, you can get a hosted database on MongoHQ.
I assume some of you would prefer a different database engine. I kept the models bare and simple, so they could easily be changed to support other database engines and schemas. Contributions more than welcome.
Although having nothing at all to do with OAuth 2.0, thought it would be cool to mention this post was drafted in full using the PlainText app on the iPad, and edited (with addition of sample code lifted from the README) on the Mac before publishing. So yes, that touch keyboard is good for a few things.
Rack::OAuth2::Server was written to provide authorization/authentication for the new Flowtown API. Thanks to Flowtown for making it happen and allowing it to be open sourced.
links for 2010-11-04 « Bloggitation