OAuth

OAuth is a protocol that allows one Web service (such as Growstuff) to connect to another Web service (such as Twitter) and perform actions on the second service on a user's behalf, without needing the user's password on the second service. We use it for linking your Growstuff account to your accounts on other services. We also use it on Pear to handle user authentication, which we delegate to GitHub.

Most of the details of the protocol are handled for us by a gem called omniauth, but it really helps to have some idea of what the gem's doing for you to use it properly.

Resources

 * RailsCast on using omniauth with Twitter
 * RailsCast on using omniauth for user login
 * The OAuth 1.0 Guide, of which this page is a summary.
 * Page on OAuth for MediaWiki developers: more ambitious than this page.

Terminology
In the classic "OAuth love triangle", there are three entities:


 * 1) the resource owner (eg the user)
 * 2) the client (eg Growstuff)
 * 3) the server (eg Twitter)

The client wants to do something on the server on behalf of the resource owner. Setups with more or fewer "legs" are possible, but we don't describe them here.

Most protocol operations involve one or more pairs of strings called credentials, made up of a token and a secret. Tokens and secrets look like random strings of letters and numbers, a few dozen characters long.

You'll occasionally see references to another random string called a nonce (a standard term in linguistics and cryptography for "word/token used only once"). A new nonce is generated for each request to prevent an attacker gaining access by replaying an observed sequence of messages (a "replay attack"). Omniauth handles this for us; if you're interested in how it works, see Security Framework in the OAuth guide.

Procedure outline
First, the client must establish a set of client credentials with the server. This is a one-time operation. Older references call these the consumer key and secret.

Now suppose a Growstuff member has clicked "Link my account to YoYoNet". The client (Growstuff) wants to get a set of credentials which gives them the required level of access to the member's account on the server (YoYoNet). These are called the token credentials, or in older references the access token and access secret. Here's how that works:


 * 1) The client generates a single-use set of credentials called the temporary credentials (aka the request token and request secret).
 * 2) The client redirects the resource owner to the server, using the temporary credentials to identify the request.
 * 3) The resource owner logs in to the server and clicks "grant access".
 * 4) The server redirects the resource owner to a callback URL on the client with the new access token and secret.

In the future, the client can make requests on behalf of the resource owner using the following
 * the client token, to identify itself
 * the access token (token token?), to identify the resource owner
 * a cryptographic hash of the client secret and the access secret, to prove it's a legit request

What do we need to implement?
For "login via Site X", we're in luck; Devise and Omniauth handle pretty much the whole thing between them. Add  to the parameters given to   in the   model.

For more complex setups, Omniauth handles the "redirect to server" part. We have to write a callback page (at ) to grab the token credentials and store them somewhere. Assuming we actually want to do something with connections to Twitter/Facebook/etc; if we just want to say "yep, that's me", we don't need to keep the token credentials. On the other hand, making everyone reconnect all their accounts so we can grab tokens would be annoying.

Omniauth also takes care of parsing the OAuth data sent by the server to the callback and putting it into a hash (which you can get with the code  in a controller method). This hash should conform to this schema. This is not completely standardized across providers, alas; for instance, there's no cross-provider way of getting a URL for "this user's profile page on that site". For Twitter, that's in. For every provider,  is a hash of URLs, but there's no deeper standardized structure.

The concrete procedure for a "connect your account to Yoyodyne" story is


 * 1) install the relevant   gem
 * 2) get a set of Yoyodyne client credentials (you'll need one for development on your local machine, and the project will need one for production and one for staging)
 * 3) add environment variables containing these credentials to credentials.sh, and blanks for them to credentials.example
 * 4) add a new   line to , referring to the environment variables you just created
 * 5) make sure /app/controllers/authentications_controller handles omniauth-yoyodyne's creative interpretation of the auth hash schema
 * 6) add links to the user profile page.

Handling client credentials
We don't want these to end up in source code; partly because it means other people can pretend to be Growstuff and do Evil Things, and partly because every Growstuff install will need its own set of credentials to prevent confusion. If you want to work on, for instance, connecting accounts to Twitter, you'll need to generate a set of client credentials for your local install by visiting https://dev.twitter.com/apps. So far we've been storing client credentials in environment variables. It's convenient to create a shell script called  containing lines like

export TWITTER_KEY=Xhg8969hslhbwhFSGHIW export TWITTER_SECRET=BR89yhg0iwehd8wybg9whgoijjg34t8HSDGWH

You can copy the file  to get started. Once you've done that, you can load all of your credentials in one go by running

source credentials.sh

This file is listed in  to prevent you from adding it to Git by accident.

What's the difference between OAuth 1.0 and OAuth 2.0?
OAuth 2.0 is apparently more complicated and flexible and enterprisey: more of a protocol framework than a single protocol. Some providers (such as Google and Facebook) use OAuth 2.0, whereas others (such as Twitter) still use OAuth 1.0. I'm hoping that Omniauth papers over the differences for us, but no doubt something will go hilariously wrong when we expand our coverage out to providers which use OAuth 2.0.