How To Set Up Devise AJAX Authentication With Rails 4.0

For whatever reason, there are several blog posts on how to accomplish this, and they all get it completely wrong. If you've found this page, I sincerely hope you haven't had to experience those. Learn from my pain.

Devise can almost do this out of the box. We just have to make some minor modifications. This post is written for people who aren't famaliar with Rails' stack of magic.

We'll make all user actions, such as /users/sign_out and /users/sign_in able to receive JSON, and respond with JSON. For example, after you sign in, the route will respond with the user in JSON. The original static pages such as /users/sign_up will still work.

Create Registration and Session Controllers

For sign up, we need to overload Devise's RegistrationsController and tell it to accept JSON. For this and the rest of this tutorial, the file names must be exact, because Ruby magic uses file names as part of how classes are found.

Create the following file: (app_root)/controllers/registrations_controller.rb with the contents:

class RegistrationsController < Devise::RegistrationsController  
    respond_to :json
end  

That's it. Seriously. If you want to stop static pages from /users/sign_in from loading, add the following line above respond_to

clear_respond_to  

For log in and log out we need to overload Devise's SessionsController. Create (app_root)/controllers/controllers/sessions_controller.rb with the contents:

class SessionsController < Devise::SessionsController  
    respond_to :json
end  

Trick Devise Into Using Our Controllers

This is simple, just add the following to your routes.rb. Replace :users with the name of your model.

devise_for :users, :controllers => {sessions: 'sessions', registrations: 'registrations'}  

Make the AJAX Requests

This section is intentionally sparse, because AJAX methods are fairly common and straightforward. Just make sure to include the CSRF token with the requests!

For convenience I have included the JSON data structure expected by each user action, and the expected response from Devise.

Sign up

URL: /users  
Method: POST  
Payload: {  
    user: {
        email: email,
        password: password,
        password_confirmation: password
    }
}
Response:  
    User JSON { "id":1,"email": ... }
    or
    { errors: { fieldName: ['Error'] }

Log in

URL: /users/sign_in  
Method: POST  
Payload: {  
    user: {
        email: email,
        password: password,
        remember_me: 1
    }
}
Response:  
    User JSON { "id":1,"email": ... }
    or
    { errors: { fieldName: ['Error'] }

Log out

URL: /users/sign_out  
Method: DELETE  
Payload: Nothing (do not send data)  
Response: Nothing  

Optional: Remove the Sign In / Sign Out Alerts

If a user signs in with AJAX and later refreshes the page, Devise still will put a banner at the top of the page through some magic injection. Let's stop that.

Edit config/locales/devise.en.yml and blank out the messages.

    sessions:
      signed_in: ""
      signed_out: ""
      already_signed_out: ""

Troubleshooting

First, make sure your files are named correctly.

In your project root on the command line run rake routes. You should see something like this:

new_user_session GET    /users/sign_in(.:format)        sessions#new  

and not this:

new_user_session GET    /users/sign_in(.:format)        devise/sessions#new  

If you see the route controllers you expect to be AJAX prefixed with devise/ then it is not correctly reading your routes.

If you have spring listed in your Gemfile, type spring stop at the command line. It may be caching your application which could prevent any of this from working. Run rake routes again and ensure the paths are correct.

Other blogs tell you to modify config/initializers/devise.rb and add this line:

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

Don't do this. You don't want your application respond to JSON requests with things like a 302 redirection. You want them to respond with JSON.

That's It!

If this post helped you navigate Rails's collection of magic, consider following me on Twitter or buying me a coffee :).

comments powered by Disqus