AJAX sign in and sign up with Devise

I would never recommend a client to have login and registration in “pop-up” dialogs instead of plain pages, but sometimes clients choose to kindly ignore my well meant advise and ask me implement such things anyway. I’ve just finished version 1 of an upcoming web application implemented with Rails 3 and – of course – Devise for authentication, and this app just happens to have sign in and sign up taking place in dialogs (or overlays) and submitted to the server via AJAX. I expected this use of Devise to be pretty common use case, but the implementation turned out be pretty tricky.

Devise is a pretty amazing authentication system. It’s modular, flexible, highly configurable and lots of extensions have already been built. It does, however, make the assumption that you want to redirect the user to a new page after he has been authenticated. When sign in takes place in a dialog that is submitted via AJAX, a redirect to the frontpage or somewhere else is not of much use. In this case we need to respond with a javascript snippet that either makes a client-side redirect, or – better yet – simply closes the sign in dialog and updates any elements on the page that depends on sign in status.

For this particular web application, the scenario was even more complicated. When an unauthenticated user wanted to comment on something, the sign in box would have to pop up in a dialog. If the user wasn’t registered with the site, he could click a link to get the sign up shown – still in the same dialog. Whether the user registered or just signed in, the dialog should finally display the form for writing a new comment. All this would have to take place through AJAX without the actual page location ever changing, and here is how I implemented it.

Devise relies on Warden for the actual bread-and-butter authentication, and Warden operates purely at the Rack level without knowing anything about your Rails app as such. When Warden encounters a user that is not signed in on a page where he was supposed to be, it will invoke a failure app: A small rack application that decides what to do with unauthenticated users. A core part of Devise is the Devise::FailureApp, which generally stores where the user wanted to go and then redirects to the sign in page. Not so for an AJAX request – or or believe the more correct term is XHR request. In this case, Devise returns a HTTP basic authentication pop-up – yikes! This even happens when http authentication has been disabled by putting config.http_authenticatable = false in config/initializers/devise.rb. Usually, we don’t want our users to get this ugly basic authentication box, so first step is to build a custom failure app that ensures that http authentication never happens through Devise:

We configure Warden to use this failure app in the devise initializer:

The observant reader will have noticed that the redirect_url method is also overridden in CustomFailure. This is necessary because Devise doesn’t pass on the format of the original request when it redirects, and we need to know in subsequent requests that we are dealing with an XHR request so we can render javascript in response rather than HTML.

That’s pretty neat so far. However, Devise is still redirecting when the user has authenticated, rather than responding with javascript to close the dialog and update the page. So we need to control what happens after a successful authentication, and that can only be done by overriding the sign_in_and_redirect method of the Devise’s SessionsController and RegistrationController. We do this by creating our own set of controllers in app/controllers/sessions_controller.rb and app/controllers/registrations_controller.rb. Our new SessionsController looks something like this:

I’ve got to admit that this is not exactly the prettiest Ruby code I have written, but I’ll leave it as an exercise to the reader to write a beautiful regular expression that can replace lines 10-12. The first couple of lines are unfortunately duplication of the original method, but the information is needed further down. If the action is invoked with an XHR request, the method takes any redirect url (the page the user originally wanted to go to) stored by Devise and makes sure that it’s called with the “js” format. Further it adds an after_authentication=true query string which was needed in this case, because the redirect was going to the CommentsController, which needed to know that it should close the sign in dialog before proceeding with displaying the comment dialog.

If there is no stored url to redirect to, we simply render the after_authentication partial, which in my case closes the sign in dialog and updates the page to indicate that the user has been logged in. If the action is invoked as a standard HTML call, the method simply redirects to super, so that we can still sign in the old fashioned way. Since I needed exactly the same code in my RegistrationsController, I put the method in a module and included that in both of the controllers. You might want to do that too.

The final missing piece is to configure Devise to use our new custom controllers rather than the built-in ones. We do that in routes.rb by means of Devise’s controller options:

Even though I’m not a big supporter of AJAX sign up and sign up – or of much use AJAX anywhere where a standard page refresh makes just as much sense – I must admit that the result was a pretty nice user experience in this case. I hope that Devise gains better support for rendering a response rather than redirecting. Until then, you can use this method.

6 thoughts on “AJAX sign in and sign up with Devise

  1. Nice post, wondering if I should implement your solution in my situation: I’ve a rails 3.0 app on devise. I want to consume this app through an API on an Android app…but I’m stuck with authentication…any clue on what I could try.

    thanks

  2. Thanks. In my experience it’s easiest to use basic HTTP authentication for API authentication. Devise already supports this – in fact, it should be automatically enabled for requests to any protected controllers that does not accept an HTML response (Ajax, XML, etc.). So no, you should not use my solution for API auth – specifically, you shouldn’t disable http auth.

  3. Thank you for this post.
    I have a problem. Success sign in works fine, but I can’t control process if password is wrong. If I understand correctly, I can do this in redirect_url, but it seems it doesn’t work for me

  4. Where can I send message like
    render :partial => ‘/shared/failed_authentication’
    in case of wrong password ?
    I’ve figured out that in case wrong password called method recall:
    def recall
    env["PATH_INFO"] = attempted_path
    flash.now[:alert] = i18n_message(:invalid)
    self.response = recall_controller.action(warden_options[:recall]).call(env)
    end
    But I can’t understand how to customize it.
    Now if I enter wrong password in ajax response I get rendering of sessions/new.html.erb. But I want to send little message, not entire page
    Thanks

  5. Any idea what to put in the registrations_controller.rb? Another Gist would be very much appreciated. Also, what goes in the sessions/new.erb.rjs file? I’m pretty new at rails, so if you could fill in the missing pieces to this tutorial for noobs such as myself, that would be very appreciated. Thanks!