Updated: 10/20/2013 Updated with better working code samples
If you’re creating an API that you yourself are consuming through a pure front-end client (along the lines of how Twitter does it) you’ll probably end up needing to track user state – sessions, basically. RESTful APIs should not be keeping track of state but unfortunately this is often unavoidable. Here I’ll go over one way to manage user state for users using your front-end client and authenticating users accessing the API programmatically (using HTTP Basic, Digest, Token authentication or something similar). My experience with this is Rails-centric as I’m rebuilding my favorite project as a pure (mostly) RESTful back-end API with a front-end API consuming client. For this particular use case you probably want to avoid reading and writing from the database as much as possible. I’ve been wanting to try out Redis for a while now and this use case has given me a perfect opportunity for that. Here’s one way to manage the state and authentication of users in a Rails app using Redis instead of a traditional session or database solution.
A hypothetical situation
If you haven’t actually built any of this, just imagine you have for the purpose of this post…
So at this point you’ve got a RESTful API on your server. It’s a “pure” API in the sense that all it does is takes HTTP verbs along with some data as input and outputs data in a serialized format (JSON, XML, etc.). The API does not respond to requests with a view object ever.
At the same time you’ve also built an API consumer client. This can be a server-side application or a client-side app that uses a JavaScript framework or something. It doesn’t matter really. The point is that your API client is a totally separate application from the API itself even if they’re hosted on the same server. In addition to your own “official” API client you have opened up your API to third party developers to build and expand upon. Your own application consumes your API just like other clients would and you require authentication through HTTP token headers.
Your app is given permissions to access all user accounts and third parties can only access public data or their own account data.
So that’s the situation we’re working with here.
Defining the problem
Our problem now is knowing if a user is a) authorized to access certain controller actions in your API and b) keeping track of authorized users.
As it relates to what’s really “RESTful”, there aren’t many good solutions here. You also want a fast API so lots of reading and writing from disk are out of the question. The three possible solutions are to use sessions, the database, or keep everything in memory (that’s where Redis comes in). Sessions aren’t really RESTful and since you’re dealing with an API you’d be forced to send a cookie to the client which only works if the client is a browser. If you use any kind of sessions you have now just barred any non-browser client from consuming your API. You could store all active tokens in your database with an expiration time and a cron job to kill sessions after that time but keeping track of sessions like that is not RESTful and it puts a lot of strain on your database with tons of reads and writes that have to take place upon every request. My solution is Redis.
Solutions
So we’ve decided to use Redis now to handle active sessions. Before we go on let’s talk about some things to consider. Redis is an in-memory key-value store. It’s like a super fast NoSQL database except it has all sorts of cool data types and functions which are perfect for temporary and/or frequently changing data. A database is best for data that needs to be persisted for long periods. Redis is great for when the data can be destroyed with no consequences. Redis can only hold as much data as you have free memory – that’s the catch. But in this case you’re only storing short strings for active users for a limited period of time. If you’ve got a server with 1G of RAM then you should be able to handle at least a few thousands active users at the same time. In my case I have Redis backed up by my database so if the server or the app restarts and Redis loses all its data the database is used as a fallback. During normal operation, however, you’d never hit the database at all.
Our tokens in this application are temporary. Each user is given an access token by first submitting their API key. From there the app verifies the identity of the key and returns an access token along with an expiration time. When the user nears the expiration time they need to resubmit their key for a new token in order to continue making requests to the app. We don’t allow the expiration to be extended just in case an attacker gets a hold of a user’s access token. They’d need their API key in order to get a new valid session. We do have facilities for applications to get special “non-expiring” tokens but we’re not going into that here.
So, in summary, we’re using Redis to track active API users via their tokens because it’s fast and cheap.
Simple token authentication
My first stab at the problem brought me to a simple solution. Just store access tokens in a database table with a belongs_to
relationship to the users table. The idea is that we’d use Rails’ authenticate_or_request_with_http_token
to capture and validate the token before any request went through as well as grab any relevant user details to pass along to the next controller which would finish processing the request. To implement this our ApplicationController
would look something like this:
1 2 3 4 5 6 7 |
|
That would work but then on top of having to do some crazy regexing to get the actual token value from the header (the This works for authenticating a valid token but you’re still hitting the database. If you’ve got to authenticate a token on each request you really don’t want to be hitting the database every time. I’ve got thousands of users to support. Thousands of users can bring millions of requests during high load periods and there’s no way I’m going to deal with so many database queries.token
variable outside of the authenticate_with_http_token
is out of scope on the third line so it needs regexing which I haven’t included here) you’d also have to hit the database with at least once upon every single request to the server.
Redis to the rescue
So we don’t really need to change our Application Controller much to support Redis instead of the database. We just need to install Redis and create a Module to help us interact with it.
Installing and Configuring Redis
The first step to getting Redis running alongside your Rails app is to install it on your server and dev machines. That’s beyond the scope of this post but you can find great instructions on the Redis website. If you’re on a Mac and have Homebrew installed, just run brew install redis
and you’re good to go. If you’re on a Linux distro, follow the official Redis website steps, don’t use your package manager as it has outdated versions of Redis. If you’re on Windows… well I wonder how you get any web development work done at all outside of using IIS and .NET.
Once Redis is installed on your machine you need a way for Rails to interact with it. Open up your Gemfile and add these lines to it:
1 2 |
|
These give you access to all the Redis commands you can find in the Redis docs and let you namespace different data you’d store in Redis.
Now let’s make sure Redis is initialized when Rails starts up. Create a file in your initializers config/initializers/redis.rb
and put the following in it:
1
|
|
And now you can use $redis
in your Rails controllers and models. But you’ll soon find that you need to access Redis from many different controllers. I first had a number of methods for interacting with Redis within my Token controller alone but then I found I needed those same methods elsewhere and didn’t want to be including random calls to Token.redis_method()
from unrelated controllers so I created a new Redis module in lib/redis.rb
lib/redis.rb
and put the following code in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Using this we can now just include RedisStore
in any controller (Application Controller if you want it available everywhere) then in the controllers you need it you just do this:
1
|
|
As a way to access data stored in your Redis datastore. So with this new module included, we can rewrite our authenticate_with_token
method like this:
1 2 3 4 5 6 7 |
|
At this point you can now use redis.hget(token, 'user_id')
RedisStore::hgetall(@token).with_indifferent_access
in the authenticate_with_token
method of ApplicationController instead of calling from the database. Using with_indifferent_access
will allow you to get the token values from anywhere in your application using symbols. So rather than calling @current_user_var[:username]
rather than @current_user_var['username']
– a small difference but I like it for readability. Using this refactored method you can now use authenticate_with_token
as a before_action
filter in front of every request and the result is just about instant because you get the result from memory, never touching the database. For tokens that do need to be stored and read from the database you could just add an elsif
clause to the method which checks for the existence of the token in the database and then stores it in Redis temporarily anyway. The point is that even if the token doesn’t expire, you only need to keep it in memory for quick access for as long as the user will need an active session.
But how do you get the token into Redis to begin with?
In our case our users will exchange their API keys for a one-time-use access token which expires after a given amount of time. When the token expires the user must reauthenticate by exchanging their key for another token.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Now you can extend this idea by adding more authorization code and permissions as you like. But at this point you should have a way to store and retrieve data from Redis. You’ll have to define all of the methods you need in the Redis module first though. My example only gives a few of the hash and other common commands. If you name the methods in the Redis module the same as the commands Redis uses to work on data then all you need to do is learn the Redis commands and they translate directly to your Rails app. Want to use the Redis hmget
command? Just call RedisStore::hmget()
. Get it? No? Read it through again and check out the Redis docs. Use their interactive examples too, they’re super awesome for learning.
In the future you can start tracking failed login attempts, spam, and create leader boards and such. The possibilities are endless. Tracking API client sessions is just the start for me.
Comments