Fun with turtles: how Songkick uses OAuth for just about everything

Nearly a year ago, Songkick waded into the weird and wonderful world of native apps. We released an iPhone app, and more recently a Spotify app. Before we launched either of these projects, we’d been letting people use Facebook to log into our website. All of which throws up the rather hairy problem of how to implement authentication.

It turns out that you can bend OAuth to allow quite a lot of authentication and authorization flows, and we decided to start using it when we developed our iPhone app. We wrote an open-source library called OAuth2::Provider to help us add OAuth 2.0 provision to Ruby web apps, and it’s served us very well when adding new applications and use cases.

What is OAuth?

If you’re not familiar with OAuth, you’ve probably used it if you’ve signed into another site using Facebook, or used various mobile applications. The basic model is quite simple: you have a ‘provider’ app, say www.songkick.com, and a ‘client’ app that’s registered with the provider. The provider issues the client with an ID and a secret, and the client tells the provider its redirect URI. When the client wants to authenticate a user using the provider, it redirects the user’s browser to the provider’s site, for example one of our apps whose redirect URI is https://www.example.com/oauth/callback might redirect to:

https://www.songkick.com/oauth/login?client_id=2sb8nskp5ijmallhnbtvj2p2u&redirect_uri=https%3A%2F%2Fwww.example.com%2Foauth%2Fcallback&response_type=code

The provider then lets the user log in via whatever process it likes, and checks that the user wants to grant the app at www.example.com access to their resources. After this process is complete, the provider redirects the browser back to the client’s redirect URI with a ‘code’:

https://www.example.com/oauth/callback?code=e6lma1389ksglysuek9okwdof

The client application then takes this code, which represents the authenticated user’s access grant, and on the server side it makes a call to the provider. It supplies the code, and also its client ID and secret, to prove it’s really the app the user has granted access to:

curl -X POST https://www.songkick.com/oauth/exchange \
     -d 'client_id=2sb8nskp5ijmallhnbtvj2p2u' \
     -d 'client_secret=d6khk8prcdpeh0zcq97pftaqz' \
     -d 'redirect_uri=https%3A%2F%2Fwww.example.com%2Foauth%2Fcallback' \
     -d 'grant_type=authorization_code' \
     -d 'code=e6lma1389ksglysuek9okwdof'

{"access_token":"4x02z6xy2c40lrn7lmt9ejnx5"}

The provider returns JSON containing an access token, which the client can then use to access a user’s data stored on the provider site. This token represents an authorization relationship between the user and client; every user-client pair gets a unique access token. This simple mechanism provides accountability of who accessed the user’s data, and it gives the user the power to revoke access for individual applications without changing their password.

How does this work in practice?

For client-side applications OAuth provides a usability and security win by relieving the app of the need to store passwords on the user’s device. This makes sure that the security credentials stored on the device only allow access to very specific things rather than the user’s entire digital life (supposing the user, as many do, use the same password for everything). It also means the user can change their password without being logged out of all their mobile applications.

The apps we’ve released for iPhone and Spotify are not web apps, but you can still use the same scheme. For the iPhone we use https://0.0.0.0/ as the redirect URI, and embed a web view containing the Songkick OAuth login page. The iPhone app monitors this embedded view to spot it redirecting to https://0.0.0.0/ so it can extract the code before making the exchange request. In Spotify we can genuinely redirect back to the app using Spotify’s routing system; the redirect URI in this case is spotify:app:songkickconcerts:action:callback.

The problem with client-side applications is that they cannot keep their client secret, well, a secret. Anyone can crack open the download and find the data in there. This is where the redirect URI comes in: imagine someone cracks our iPhone app and extracts the client_id, client_secret and redirect_uri. They could put up a malicious site at www.evil.com and ask users to log in via Songkick, trying to get users to expose their data to www.evil.com. But the provider only processes the request if the redirect_uri is the one registered for that client_id, so the attacker is forced to use the real redirect_uri when redirecting to www.songkick.com if they want the user to log in. But then Songkick will redirect the user’s browser back to the real application, and www.evil.com will never get the code it so desperately wants.

What about Facebook?

As I mentioned, Songkick lets people log in using Facebook, and we needed to continue to let them do that in our native apps so they could access the same account everywhere. OAuth has two features that help with implementing this and we’ve tried both of them.

Assertions

The first of these features is assertions. Instead of sending the user to the provider’s site to make them log in, the client can just pass some authentication token it already has from somewhere else. If that token is unguessable and can be used to identify the user, it can take the place of the code in the exchange request. Say the client app has somehow acquired a Facebook access token for the user (Facebook also implement OAuth 2.0 for their Open Graph system), it can then make one request to authenticate the user with Songkick, using the grant_type=assertion exchange:

curl -X POST https://www.songkick.com/oauth/exchange \
     -d 'client_id=2sb8nskp5ijmallhnbtvj2p2u' \
     -d 'client_secret=d6khk8prcdpeh0zcq97pftaqz' \
     -d 'redirect_uri=https%3A%2F%2Fwww.example.com%2Foauth%2Fcallback' \
     -d 'grant_type=assertion' \
     -d 'assertion_type=https%3A%2F%2Fgraph.facebook.com%2Fme'
     -d 'assertion=the_facebook_access_token'

{"access_token":"4x02z6xy2c40lrn7lmt9ejnx5"}

The assertion_type is some arbitrary URI the provider uses to identify the type of credential being used in the assertion. When Songkick receives this, it makes a call to the Facebook Open Graph:

curl https://graph.facebook.com/me?oauth_token=the_facebook_access_token

This gives us the user’s details and we map those to a Songkick account; we then return our own access token for that account and the iPhone app can then interact with the user’s data. To get the user’s Facebook token it interacts with the Facebook application on the device before communicating with Songkick’s servers.

We also use assertions to provide a useful experience on the iPhone before the user creates an account. We use the phone’s UDID as an assertion and exchange it for a Songkick access token. This way we can personalize the app without the user needing to sign in first.

The state parameter

When we started on the Spotify app, we knew we didn’t want to reimplement Facebook login for that application. We didn’t want to redeploy all our native apps every time we needed to fix a bug from interacting with a third party, or when we wanted to add new login mechanisms. We decided the app should only know how to talk to www.songkick.com, and our website would provide all the login mechanisms we need.

Remember that the provider site is free to implement authentication in whatever way it likes, as long as it eventually redirects to the client with a code. This means we can offer the choice of username/password or Facebook authentication on our website without the client having any idea this choice exists. Recall that the client will redirect to us with a request like:

https://www.songkick.com/oauth/login?client_id=2sb8nskp5ijmallhnbtvj2p2u&redirect_uri=https%3A%2F%2Fwww.example.com%2Foauth%2Fcallback&response_type=code

If the user chooses to log in with Facebook, we’ll make a similar redirect to Facebook, passing our own OAuth client credentials for their service. In other words, our OAuth login page contains a link to

https://www.facebook.com/dialog/oauth?client_id=...&scope=...&redirect_uri=...&state=...

If the user clicks the link, they’ll go through an OAuth transaction on facebook.com and be redirected back to us. The important thing here is the state parameter: an OAuth provider is required to echo this value back unmodified when they redirect back to the client; this is so the client can figure out what it was doing before sending the user off to authenticate and can resume its work.

In our case, this means picking up the conversation with the OAuth client talking to us: the Spotify app. Once we’ve received a code from Facebook and converted it into a Songkick account, we just need to know where to redirect back to with a code for this Songkick account. What we do is, we take the params the client called us with, that is:

params = {
  "client_id"     => "2sb8nskp5ijmallhnbtvj2p2u",
  "redirect_uri"  => "https://www.example.com/oauth/callback",
  "response_type" => "code"
}

Then we convert them into a string for the state parameter we send through Facebook. We JSON-encode them, and Base-64-encode that JSON document, and we also compute an HMAC-SHA1 tag so we can make sure the value is not modified on its way back to us:

string = Base64.encode64(JSON.dump(params)).strip
sha1   = OpenSSL::Digest::Digest.new('sha1')
tag    = OpenSSL::HMAC.hexdigest(sha1, SECRET, string)
state  = string + ':' + tag

When Facebook redirects back to us with a code and state, we use the code to get the user’s Facebook data and map this to a Songkick account, then we unpack the state and use it to reconstruct the original OAuth request to us. We generate a code for the Songkick account and send it back to the client, and the client has no idea how the user actually logged in.

There’s one obvious way to do it

All of this is a little confusing at first, and trying to discuss recursive OAuth transactions certainly eats up a lot of whiteboard space, but the benefit of having one standardized protocol for authentication and authorization is huge. Authentication code can be one of the messiest parts of an application, and getting it wrong can have dire consequences. It’s also something you don’t want to reinvent in every application, and using a protocol as adaptable and extensible as OAuth really helps on this front. We’ve even started using it internally, so that instead of having one ‘traditional’ login page and one OAuth page, we’re beginning to route website logins through our OAuth endpoint and using an internal client to turn a normal login request into an OAuth one, so we can route it through the same logic as everything else. It’s a big maintenance win, and using an open standard should mean it’s friendlier to newcomers trying to maintain it.