Implementing Oauth On Top of Redis: Problem
Implementing Oauth On Top of Redis: Problem
Problem
In this recipe, we’ll implement a data model and interaction to support an OAuth v1.0a
API. This is usually achieved on top of MySQL or another RDBMS, but we’ll leverage
Redis’s data structures for a more efficient implementation.
Solution
We won’t be implementing the API or the OAuth interaction itself. Here we’re inter-
ested only in the data required for this sort of scenario. We’ll be storing five types of
data in Redis:
consumer keys
consumer secrets
request tokens
access tokens
nonces
So the needs are as follows: applications (consumers) are identified by their key and
secret, of which they have exactly one pair. Those consumers can have as many request
and access tokens as they desire, and the nonces should be unique per consumer/time-
stamp pair.
These types of data will be stored in hashes, sets, and strings depending on their specific
requirements and interactions.
Discussion
Initial setup
To start with, consumers must enter their data before they issue a request. Let’s put
this data in a hash with the consumer information. The key is the one we’ve stored for
the particular consumer when he or she registered with our system:
HMSET /consumers/key:dpf43f3p2l4k3l03 secret kd94hf93k423kf44 created_at 201103060000
redirect_url http://www.example.com/oauth_redirect name test_application
This command gives us, for every application, a hash containing its “general” data,
which can be extended over time. The same could be achieved in Memcache either by
www.it-ebooks.info
storing all the values in different keys or by storing the data in some format like JSON
or YAML.
and then check that this nonce hasn’t been used yet:
SADD /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 dji430splmx33448
Using a set to store the nonce data has a few advantages. First, sets assure uniqueness
(we’ll see in a minute how to tell whether the element was present already). Also, we
can delete all the nonces for past requests after a chosen period of time, by setting
expiration times on each one. For this application, let’s make the expiration time 30
minutes.
EXPIRE /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 1800
Now, if someone was to attempt to replay this request by sending the same timestamp
and nonce, issuing the same SADD command as before would return 0, indicating that
this value was already present in the set. Should this happen, the provider should refuse
to generate a new token.
After validating all the data, we need to create a token and matching secret:
HSET /request_tokens/key:dpf43f3p2l4k3l03 hh5s93j4hdidpola hdhd0244k9j7ao03
www.it-ebooks.info
Quick Reference for Authorization Algorithm
HGETALL hash-name
Returns all the key/value pairs in the given hash.
SADD set-name element
Adds the element to the given set unless it’s already a member. The return value
is 1 if the element is added and 0 if it was already a member.
EXPIRE key seconds
Sets an expiration timeout on a key, after which it will be deleted. This can be used
on any type of key (strings, hashes, lists, sets or sorted sets) and is one of the most
powerful Redis features.
EXPIREAT key timestamp
Performs the same operation as EXPIRE, except you can specify a UNIX timestamp
(seconds since midnight, January 1, 1970) instead of the number of elapsed sec-
onds.
TTL key
Tells you the remaining time to live of a key with an expiration timeout.
PERSIST key
Removes the expiration timeout on the given key.
Once that is done, we can redirect the user to the redirect URL we stored:
HGET /consumers/key:dpf43f3p2l4k3l03 redirect_url
As for the previous operations, we need to check whether the consumer key is valid
and matches an existing application.
HGET /request_tokens/key:dpf43f3p2l4k3l03 hh5s93j4hdidpola
www.it-ebooks.info
We also need to check the request token and a failure to find it would probably mean
someone is attempting to reuse a request token which is not allowed by the spec.
GET /authorizations/request_token:hh5s93j4hdidpola
The last thing we need to check is which user authorized this application.
SADD /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182700 kllo9940pd9333jh
EXPIRE /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 1800
Once again, the nonce should be unique for this consumer—the output of SADD suffices
as sets assure uniqueness. A failure in any of the checks implies an invalid request and
therefore we shouldn’t generate an access token. If everything is OK, we can proceed:
HMSET /access_tokens/consumer_key:dpf43f3p2l4k3l03/access_token:nnch734d00sl2jdk
secret pfkkdhi9sl3r4s00 user_id 16 created_at 20110306182600
HDEL /request_tokens/key:dpf43f3p2l4k3l03 hh5s93j4hdidpola
DEL /authorizations/request_token:hh5s93j4hdidpola
Perhaps somewhere in our application we allow users to see which applications have
access to their credentials. To facilitate the retrieval of that information, let’s add it to
a hash of client applications:
HSET /users/user_id:16/applications dpf43f3p2l4k3l03 nnch734d00sl2jdk
Our application logic might also define different expiration times for each new token,
perhaps even at the user’s request. Let’s say that in this case the user gave permission
for 24 hours (86400 seconds):
EXPIRE /access_tokens/consumer_key:dpf43f3p2l4k3l03/access_token:nnch734d00sl2jdk
86400
Beware of one detail: if you are expiring the access tokens, you need either to check for
their existence (and remove them from the hash if they’re absent) before presenting the
user with the list of authorizated applications, or to do a regular clean-up operation
that checks that the keys in the /users/user_id:16/applications hash are still valid.
www.it-ebooks.info
API Access
When the consumer is accessing the API, the process should be really simple: validate
the keys, secrets, signatures, and nonce.
HGETALL /consumers/key:dpf43f3p2l4k3l03
HGETALL /access_tokens/key:dpf43f3p2l4k3l03/access_token:nnch734d00sl2jdk
SADD /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182800 kllo9940pd9333jh
EXPIRE /nonces/key:dpf43f3p2l4k3l03/timestamp:20110306182600 1800
Solution
Since Redis has native support for the publish/subscribe (or pub/sub) pattern, we can
easily use it in conjunction with Node.js and Socket.IO to quickly create a real-time
chat system.
The publish/subscribe pattern defines a way in which receivers subscribe to messages
that match a specific pattern (for instance, messages that are sent to a specific “chan-
nel”), and a way for an emitter to send messages to a message cloud. When a message
hits that cloud, clients that subscribe to messages of that kind will get the message. The
pattern allows then for emitters and clients to be loosely coupled—they don’t need to
know each other. They just need to be able to send messages in a given pattern, and
receive messages that match that pattern.
For a better understanding of how Publish/Subscribe works, see the Wikipedia page.
Redis has direct support for the pub/sub pattern, meaning that it lets clients subscribe
to specific channels matching a given pattern, and to publish messages to a given chan-
nel. This means that we can easily create channels like “chat:cars” for car-talk, or
“chat:sausage” for food-related conversation. The channel names are not related to the
Redis keyspace so you don’t have to worry about conflicts with existing keys. The pub/
sub functionality is supported by the following Redis commands:
PUBLISH
Publishes to a specific channel
SUBSCRIBE
Subscribes to a specific channel
UNSUBSCRIBE
Unsubscribes from a specific channel
www.it-ebooks.info