Roll Your Own oAuth if You Dare

It’s common for experienced developers to tell you to never ever roll your own oAuth solution. Hell, you really shouldn’t roll your own anything when it comes to security. After all, oAuth, user authentication, and the like aren’t like coding a blog or something. It requires forethought and lots of planning. There are a lot of holes to slip through. That said, sometimes it’s not practical to integrate an existing solution into your app. Sometimes you need to allow API consumers quickly. Don’t let the cool kids scare you. It’s definitely possible to roll your own, much simpler, version of oAuth like the one I’m working on for Write.app. Follow along and I’ll show you how to build a very simple, straightforward oAuth scheme that requires only two database tables.

Update: This post is scheduled for much editing. There’s a lot in here that I think needs to be explained better.

The Flow

In my case I needed to add an oAuth solution to Write.app that allowed registered API applications authenticate then read and write data on behalf of the user’s who authenticated through them.

My solution is very oAuth-like but not truly oAuth if you want to follow the spec. So instead of calling it oAuth let’s call it wAuth (the ‘W’ is for Write.app, where this scheme is being implemented).

So here’s how the flow would work:

1. Registering an app and definitions

User registers a new API application and is given a API Key, Secret Key, and Access Token. Each one of these has a special purpose. Let’s get into that real quick.

  • API Key – This is the third party app’s identifier. Rather than using the app creator’s user ID, we generate a random string that becomes the app’s user ID. In my case I just md5 a random string. We want them to be unique but API keys aren’t that sensitive in our application.
  • Secret Key – This is equivalent to the app’s password. This one is an sha1 hashed value that comes from values unique to the user and a secret unique to Write.app itself.
  • Access Token – This along with the API key will allow a user to access their own account right away using the API. It is equivalent to a user ID. Rather than making the user who generates an API application authenticate using their own application, we just do it for them behind the scenes. The reason for this is that sometimes a user just wants to access their own data and will never authenticate any other users.

2. Accessing a user’s account

The API application (a.k.a consumer) will eventually want to access another user’s account. To do this it must authenticate via our oAuth scheme. The app sends the user to the provider (your web app that implements oAuth) at a special URL along with an HTTP Authorization header. Now we begin to really differentiate from normal oAuth. In a true oAuth scheme, the API application would request a token from the provider first, then send the user they want to authenticate to that special URL along with the token the provider sent back. In our case we’re skipping this step and instead we only require that the API consumer authenticate with HTTP Basic Authorization.

You may be thinking I’m crazy for allowing API consumers to authenticate via Basic auth. I’m not. I’m using SSL everywhere. The only way anyone’s API key and secret are being stolen is if my server is compromised or a client is severely compromised. If you’re allowing any non-SSL conncections to your API then you are screwed. Get an SSL certificate for free before continuing on.

So, we have an API consumer that wants to access a user’s private data. That API consumer will redirect the user to the user authentication endpoint https://api-provider.com/authenticate along with their API key and secret in an HTTP Basic header Authorization: Basic api_key:secret_key.

The API provider then authenticates that a registered API consumer is trying to access it using the information from the Authorization header. Once authenticated the user is shown a web page. If the user is already logged into the API provider then they’ll see a page that says something like “App XYZ is asking for permission to access your account” with the option for the user to allow or deny the request. If the user is not logged in they’ll need to log in first then be redirected to this permissions page.

Once the user grants the API consumer access to the account, an access token is created. That access token is posted back to a callback URL that the API consumer registers when generating their API credentials. That same token is also saved in a database table alongside the user’s ID and the API consumer’s unique ID (API key).

By now the user should be authenticated and sent back on the API consumer’s website provided they allowed the consumer access to their account.

From this point on, whenever the API application wants to read or write data to that user’s account they send their API credentials in an Authorization header as well as the access token for that user either in the URL or in the POST data. The access token is not secret information. It’s equivalent to a user ID and is useless unless you have the API key and secret that correspond to the API consumer that has permission to access that user’s account.

Pretty simple right?

To actually implement this scheme, here’s some example code in PHP:

Database tables needed

First, we need a place to store our API credentials for each user:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE `api_keys` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(10) NOT NULL,
  `key` char(60) COLLATE utf8_bin NOT NULL,
  `level` int(2) NOT NULL,
  `ignore_limits` tinyint(1) NOT NULL,
  `date_created` varchar(11) COLLATE utf8_bin NOT NULL,
  `secret_key` varchar(60) COLLATE utf8_bin NOT NULL,
  `app_name` varchar(150) COLLATE utf8_bin NOT NULL,
  `description` text COLLATE utf8_bin NOT NULL,
  `callback` varchar(255) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`)
);

The important columns here are:

  • user_id – The user the API app/consumer is registered to
  • key – The API key. Identifies the application
  • secret_key – This, when presented with the API key authenticates an API consumer
  • app_name and description – These two items are displayed on the oAuth page where user’s give the consumer permission to access their account. You’ve seen this before with Twitter and Facebook login screens.
  • callback – Every API consumer needs a callback URL in order to authenticate other users. When a user gives an API consumer permission to access their account the generated access token is sent to this URL. Leaving this blank means that there’s no way for the API provider to send access tokens and thus you cannot access a user’s account.

Next, you need a permissions table to track which applications have access to which users’ accounts:

1
2
3
4
5
6
7
8
CREATE TABLE `permissions` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `user_id` int(10) NOT NULL,
  `requestor` varchar(60) COLLATE utf8_bin NOT NULL,
  `access_token` varchar(60) COLLATE utf8_bin NOT NULL,
  `date_created` varchar(10) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`)
);

The relevant columns here are:

  • user_id – The user ID that the API consumer has permissions for
  • requestor – The API key of the API consumer. Once a consumer has been authenticated we check the permissions table and can say “okay, does the app with API key XYZ have an association with the access token they just sent?”
  • access_token – This is a unique value generated for each user/API consumer pair. One user can have an unlimited amount of access tokens associated with their account. Each access token allows a different API consumer to access their account. This also allows us to show a list of authorized API applications to a user who can then revoke permissions to that app.

The other columns should be self-explanatory. If they aren’t then I can’t in good faith tell you to implement this oAuth-like scheme.

Endpoints

You’ll need a series of endpoints to handle authentication. These would be:

/api/authorize – This is where you send a user to authorize your API application.

That’s it. Simple, right? That’s the only endpoint you need to implement to allow API consumers to access another user’s account. Obviously you’ll need to handle key generation, storage, and revocation but as far as third-party access, that’s it.

Web development

« Run a VPS? You are being hacked right now Sometimes the best new feature is no new features »

Comments