64x64 icon dark hosting
Deploy New Relic now and get $135 off your Tuts+ subscription.
Advertisement

How to Build an Unobtrusive Login System in Rails

by

An unobtrusive login system is one that gets out of the user’s way. It will make your application nicer and more polished. This article will guide you through the process of setting up user logins, then ajaxifying the process by moving the form into a modal box that communicates with the server. Additionally, this article will show you how to create the setup using jQuery and Prototype so you can choose your favorite library. This article assumes that you have experience with Rails and jQuery or Prototype.


You can use Adman65/nettuts for a successful login. Be sure to use bad credentials so you can see how everything works.

What We're Making

Getting Started

We’re going to start by creating a dummy application that has a public and private page. The root url is the public page. There’s a login link on the public page. If the user logs in successfully, they’re redirected to the private page. If not, they’re redirected back to the login form. The private page shows the user name. We’ll use this as the starting point for ajaxifying the site.

The first step is using the rails command to generate a new application, then install and setup up authlogic.

Add authlogic.

Now install the gems.

Next create a user model and add the required authlogic columns to the migration.

Now, add the columns to the new migration.

Migrate the database.

Include authlogic in the user model.

Now we can create a user. Since this is a demo app, web based functionality for signing up isn’t required. So open up the console and create a user:

Now we have a user in the system, but we have no way to login or logout. We need to create the models, controllers, and views for this. Authlogic has its own class for tracking logins. We can use the generator for that:

Next we need to generate the controller that will login/logout users. You can create sessions just like any other resource in Rails.

Now set its contents to:

It looks exactly the same as a controller that was generated via scaffolding. Now create the users controller which has public and private content. Generate a users controller. Inside the controller we’ll use a before filter to limit access to the private areas. The index action is public and show is private.

Update its contents:

You should notice that current_user is an undefined method at this point. Define these methods in ApplicationController. Open up application_controller.rb and update its contents:

At this point the models and controllers are complete, but views aren’t. We need to create views for a login form and the public and private content. We’ll use the nifty-generators gem to create a basic layout.

Time to create the login form. We’re going to use a partial for this because in the future we’ll use the partial to render just the login form in the modal box. Here’s the code to create the login form. It’s exactly the same as if you were creating a blog post or any other model.

Render the partial in the new view:

Create some basic pages for the public and private content. The index action shows public content and show displays private content.

And for the private page:

Delete the file /public/index.html and start the server. You can now log in and logout of the application.

Here are some screenshots of the demo application. The first one is the public page.

Public Page

Now the login form

Login Page

And the private page

Private Page

And finally, access denied when you try to visit http://localhost:3000/user

Access Denied

The AJAX Login Process

Before continuing, we need to understand how the server and browser are going to work together to complete this process. We know that we’ll need to use some JavaScript for the modal box and the server to validate logins. Let’s be clear on how this is going to work. The user clicks the login link, then a modal box appears with the login form. The user fills in the form and is either redirected to the private page, or the modal box is refreshed with a new login form. The next question is how do you refresh the modal box or tell the browser what to do after the user submits the form? Rails has respond_to blocks. With respond_to, you can tell the controller to render different content if the user requested XML, HTML, JavaScript, YAML etc. So when the user submits the form, the server can return some JavaScript to execute in the browser. We’ll use this render a new form or a redirect. Before diving any deeper, let’s go over the process in order.

  1. User goes to the public page
  2. User clicks the login link
  3. Modal box appears
  4. User fills in the form
  5. Form is submitted to the server
  6. Server returns JavaScript for execution
  7. Browser executes the JavaScript which either redirects or updates the modal box.

That’s the high level. Here’s the low level implementation.

  1. User visits the public page
  2. The public page has some JavaScript that runs when the DOM is ready that attaches JavaScript to the login link. That javscript does an XMLHTTPRequest (XHR from now on) to the server for some JavaScript. The JavaScript sets the modal box’s content to the form HTML. The JavaScript also does something very important. It binds the form’s submit action to an XHR with POST data to the form’s action. This allows the user to keep filling the login form in inside the modal box.
  3. Modal box now has the form and required JavaScript
  4. User clicks ‘Login’
  5. The submit() function is called which does a POST XHR to the form’s action with its data.
  6. Server either generates the JavaScript for the form or the redirect
  7. Browser receives the JavaScript and executes it. The browser will either update the modal box, or redirect the user through window.location.

Taking a Peak at the AJAX Ready Controller

Let’s take a look at the new structure for the UserSessions controller.

As you can see the structure is different. Inside the if save, else conditional, respond_to is used to render the correct content. want.xx where xx is a content type. By default Prototype and jQuery request text/JavaScript. This corresponds to wants.js. We’re about ready to get started on the AJAX part. We won’t use any plugins except ones for modal boxes. We’ll use Facebox for jQuery and ModalBox for Prototype.

Prototype

Rails has built in support for Prototype. The Rail’s JavaScript helpers are Ruby functions that generate JavaScript that use Prototype. This technique is known as RJS (Ruby JavaScript). One example is remote_form_for which works like the standard for_for adds some JS bound to onsubmit that submits to the form to its action using its method with its data. I won’t use RJS in this article since I want to demonstrate vanilla JS. I think by using pure JS and eliminating the JS helpers the article will be more approachable by less experienced developers. That being said, you could easily accomplish these steps using RJS/Prototype helpers if you choose.

Adding Prototype to the application is very easy. When you use the rails command, it creates the Prototype and scriptaculous files in /public/JavaScripts. Including them is easy. Open up /app/views/layouts/application.erb and add this line inside the head tag:

JavaScript_include_tag creates script tags for default files in /public/JavaScripts, most importantly prototype.js, effects.js, and application.js. effects.js is scriptaculous. application.js is a file you can use to keep application specific JS. Now we need a modal box plugin. We’re going to use this. Its a very nice modal box plugin inspired by OSX. The source is hosted on GitHub, so you’ll have to clone and move the files in your project directory. For example:

Now include the stylesheets and JavaScript in your application.

Now let’s get our login link to open a modalbox. In order to do this we need to add some JavaScript that runs when the DOM is ready that attaches the modalbox to our link. When the user clicks the login link, the browser will do a GET to /user_sessions/new which contains the login form. The login link uses the #login-link selector. Update the login link to use the new id in /app/views/users/index.html.erb. Modify the link_to function like this:

That gives us a#login-link. Now for the JavaScript to attach a modalbox. Add this JS in /public/JavaScripts/application.js

There’s some simple JS for when the user clicks the link a modal box opens up with the link’s href. Refer to the modalbox documentation if you’d like more customization. Here’s a screenshot:

Initial Modalbox

Notice that inside the modal box looks very similar to our standard page. Rails is using our application layout for all HTML responses. Since our XHR’s want HTML fragments, it make sense to render without layouts. Refer back to the example controller. I introduced a method for determining the layout. Add that to UserSessionsController to disable layout for XHR’s.

Refresh the page and click the link you should get something like this:

Without Layout

Fill in the form and see what happens. If you fill in the from with bad info, you’re redirected outside the modal box. If you login correctly you’re redirected normally. According the requirements the user should be able to fill out the form over and over again inside the modal box until they login correctly. How can we accomplish this? As described before we need to use AJAX to submit data to the server, then use JavaScript to update the modal box with the form or do a redirection. We know that the modalbox does a GET for HTML. After displaying the initial modalbox, we need to write JS that makes the form submits itself AJAX style. This allows the form to submit itself inside the modal box. Simply adding this code after the modal box is called won’t work because the XHR might not have finished. We need to use Modalbox’s afterLoad callback. Here’s the new code:

Form#request is a convenience method for serializing and submitting the form via an Ajax.Request to the URL of the form’s action attribute—which is exactly what we want. Now you can fill in the form inside the modal without it closing. The client side is now complete. What about the server side? The client is submitting a POST wanting JS back. The server needs to decide to either return JavaScript to update the form or render a redirect. In the UserSessionsController we’ll use respond_to to handle the JS request and a conditional to return the correct JS. Let’s begin by handling the failed login case. The server needs to return JS that updates the form, and tells the new form to submit over ajax. We’ll place this template in /app/views/users_sessions/create.js.erb. Here’s the structure for the new create action:

Now let’s fill in create.js.erb:

First we update the content to include the new form. Then we resize the modal box. Next we ajaxify the form just as before. Voilla, you can fill in the form as many times as you want.

Bad Info
Updated Form

Next we need to handle the redirection case. Create a new file in /app/views/users_sessions/redirect.js.erb:

Now, update the create action to handle the redirection process:

And that’s it! Now try login with correct credentials and you’re redirected to the private page. For further learning, try to add a spinner and notification telling the user the form is submitting or they’re being redirect. The application still works if the user has JavaScript disabled too.

jQuery

Since I’ve already covered the Prototype process, so I won’t go into the same detail as before. Instead, I will move quickly describing the alternate JavaScript to add to the application. The jQuery vesion will have the exact same structure as the Prototype version. All we need to change is what’s in application.js, create.js.erb, and the JavaScript/css includes.

First thing we need to do is download jQuery and Facebox. Move jQuery into /public/JavaScripts as jquery.js. For facebox move the images into /public/images/, stylesheets into /public/stylesheets, and finally the JS into /public/JavaScripts. Now update /app/views/layouts/application.html.erb to reflect the changes:

Facebox comes with a default stylesheet which assumes you have your images in /facebox. You’ll need to update these selectors in facebox.css like so:

Now we attach facebox to the login link. Open up /public/JavaScripts/application.js and use this:

I override the default settings for the images to reflect the new image path. Start the sever and head over to the index page. You should see a nice facebox with the login form:

Facebox

Next thing we have to do is set the form to submit itself via AJAX. Just like before, we’ll have to use callbacks to execute code after the modal box is ready. We’ll use jQuery’s post method for the XHR request. Facebox has an after reveal hook we can use. application.js:

Updating create.js.erb should be easy enough. We have to update the facebox’s contents and re-ajaxify the form. Here’s the code:

And that’s it! Here’s the final product:

Logging In
Bad Login
Redirected

Downloading the Code

You can get the code here. There are branches for each library so you can check out the Prototype or jQuery versions. Any questions, comments, concerns? Thanks again for reading!

Advertisement