JSON API Authentication using Devise tokens

For an app that I’m working on I want users to be able to create an account and login. The app speaks JSON. I thought this would be trivial with Devise using something like TokenAuthenticatable, but unfortunately that got deprecated without too much information about alternatives. In typical open source euphemism:

we have decided to remove TokenAuthenticatable from Devise, allowing users to pick the best option

When a user logs in on a website that uses Devise, the browser receives a cookie with session information. If you’re using database sessions, the cookie only contains a session id. The cookie is passed along with every request so that you don’t have to send the username and password every time.

If I understand correctly, you can’t or shouldn’t use cookies with JSON. Instead of using a cookie with the session id, the server gives you a token during login which you then add to the HTTP headers of each subsequent request. The simple_token_authentication does most of this magic for you. The only thing I changed is setting authentication_token to nil at logout so that if the token is compromised it can’t be used again.

Ingredients

I posted the code on Github.  

I needed to subclass the Devise registrations and sessions controller and set

 config.navigational_formats = ["/", :json]

in the initializer.

I disabled the default CSRF protection for JSON requests by placing this line in application.rb:

protect_from_forgery with: :null_session, :if => Proc.new { |c| c.request.format == 'application/json' }

To determe which user can see what, it uses CanCan for authorization, which requires a tweak in Rails 4.

Create account

I used Curl to test the API before building the app. First, let’s create a new user:

curl -H "Content-Type: application/json" -d '{"user":{"email":"sjors@purpledunes.com","password":"12345678"}}' -X POST http://localhost:3000/users.json

Login and logout

curl -H 'Content-Type: application/json'   -H 'Accept: application/json' -X POST http://localhost:3000/users/sign_in   -d '{"user": {"email": "sjors@purpledunes.com", "password": "12345678"}}'

If successful this returns:

{"user":{"id":1,"email":"sjors@purpledunes.com"},"status":"ok","authentication_token":"PLuoQWHf6z_qxzacXBGY"}

Keep this token around for later use:

export EMAIL=sjors@purpledunes.com
export TOKEN=PLuoQWHf6z_qxzacXBGY
export ROOT_URL=http://localhost:3000

Later you can logout so that this token can no longer be used:

curl -i -H "Accept: application/json" -H "Content-type: application/json" -H "X-User-Email: $EMAIL" -H "X-User-Token: $TOKEN" -X DELETE $ROOT_URL/users/sign_out

CRUD

The most important thing to do on the internet is maintaining a list of cats. Any time you want to create, update or delete a cat or see a list of your cats, you’ll need to add your email and authentication token to the request headers. So to add a cat:

curl -i -H "Accept: application/json" -H "Content-type: application/json" -H "X-User-Email: $EMAIL" -H "X-User-Token: $TOKEN"  -X POST -d '{"cat": {"name":"Felix Domestica Prima" } }' $ROOT_URL/cats.json

To list your cats:

curl -i -H "Accept: application/json" -H "Content-type: application/json"  -H "X-User-Email: $EMAIL" -H "X-User-Token: $TOKEN" -X GET $ROOT_URL/cats.json
{"cats":[{"id":1,"name":"Felix Domestica Prima"}]}

To change or delete a cat:


curl -i -H "Accept: application/json" -H "Content-type: application/json" -H "X-User-Email: $EMAIL" -H "X-User-Token: $TOKEN" -X PATCH -d '{"cat": {"name":"Unit"} }' $ROOT_URL/cats/1.json
curl -i -H "Accept: application/json" -H "Content-type: application/json" -H "X-User-Email: $EMAIL" -H "X-User-Token: $TOKEN" -X DELETE $ROOT_URL/cats/1.json

The Real World

Before you deploy this, you should at the very least force an HTTPS connection so that people monitoring the communication can not simply read the password or token. This is pretty simple on Heroku using force_ssl and you don’t need your own certificate.

Issues

  • Currently only one app can login at the same time for each user, because the token is replaced during login. One solution could be to modify  simple_token_authentication to store an array of tokens. See this issue.
  • If you try to access any of the JSON URLs when you’re not logged in, it redirects you to an HTML page.

Leave a comment

Your email address will not be published. Required fields are marked *