RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


OpenID and Rails: Authentication 2.0 : Page 3

Just about every web application uses the username and password combination for authentication. OpenID offers a better way. Learn how to integrate OpenID authentication in your Rails 2.0 applications.

Add Some Intelligence to Your Authentication System
Now that all the pieces are in place, it is time to start modifying the application code. Open the file app/controllers/application.rb and add the following authorize method:

def authorize
  unless session[:user_id]
    flash[:notice] = "Please log in"
    # save the URL the user requested so we can hop back to it
    # after login
    session[:jumpto] = request.parameters
    redirect_to(:controller => "/login", :action => "index")

This method will intercept all the users' requests for actions that cannot be performed without valid credentials and redirect them to the login screen. By storing the request parameters into the session :jumpto symbol, this method also guarantees that, immediately after login, the user will be forwarded to the URL that he initially requested.

You now have to modify the todos_controller.rb that was created during the previous scaffolding. Add the before_filter as follows:

class TodosController < ApplicationController
  before_filter :authorize, 
    :only => [ :new , :edit, :create, :update, :destroy]
  ... rest of the controller as before ...

This ensures that all the actions that create or modify a to-do item are accessed only after a successful login.

You can now move to the core of the login mechanism by implementing the login controller. On a terminal, launch this:

# script/generate controller login

Open the generated app/controllers/login_controller.rb file, and edit it so that it matches the contents of Listing 1. This controller contains all the magic, so take a close look at it.

The index method either redirects the user to the web site main page (http://localhost:3000/todos) if he already performed login, or forwards him to the login page (http://localhost:3000/login). The view for the login page is still missing, so create the file app/views/login/index.html.erb with these contents:

<% if flash[:error] -%>
  <%= flash[:error] %>
<% end -%>
<% form_tag :controller => "login" , :action => "login" do |f| -%>
  <label for="openid_url" >
    OpenId URL:
  <%= text_field_tag :openid_url -%>
  <%= submit_tag "Login" -%>
<% end -%>

As you can see, instead of the traditional username and password fields, there is now only one text field, which will accept the user's OpenID identifier. The label openid_url is a conventional one that will allow the browser to remember the user's OpenID identifier across different OpenID-enabled web sites.

Moving on, the login and logout methods, respectively, are responsible for handling the login process and for cleaning the session of all the user's data once he logs out of your application. The using_open_id? function belongs to the ruby-openid library, and it detects whether the user is initiating or finalizing a login. Remember that, as described in Figure 3, the login process involves two calls to your web site: the first containing the OpenID identifier to initiate the login process and the second containing the user credentials as returned by the OpenID provider. Therefore, the login method ends up being called twice and the using_open_id? function detects both occurrences (more on this later).

The authenticate function contains the bulk of the authentication logic. It delegates to the authenticate_with_open_id function within the ruby-openid library. This function accepts a block that will execute once the login completes (either successfully or not)--that is, after the second call back to the web site. The block accepts three parameters:

  • The result object defines the effectiveness of the login process. It contains methods to investigate the various possible failures (user has canceled login, OpenID provider unavailable, etc.).
  • The identity_url object contains the user OpenID identifier.
  • The registration object contains additional information about the user, according to the Simple Registration Extension for OpenID (SREG) specification.

The Simple Registration Extension is an add-on to the OpenID specification that allows providers and web sites to exchange additional information about the users. This may include the user's e-mail, real name, date of birth, and other personal data. To respect the user's privacy, each user can instruct the OpenID provider not to disclose this kind of information. In the previous example, you used the :required directive (as opposed to the :optional one) to require a nickname and email address along with the user's credentials.

SREG is another way in which OpenID improves the authentication experience when compared with traditional mechanisms, where each web site would require the user to complete a registration form, specifying again and again his personal data to each interested third party.

Lastly, the authenticate method stores all the user data into the :user_id session variable and redirects the user to the originally requested page with the redirect_to(:jumpto) statement.

As you already know, the login method will be called twice--the second time being a redirect instructed from the OpenID provider to the user browser. In order for this to work, you have to perform two last steps. First, you have to define an appropriate route in the routes.rb configuration file, as follow (notice that you're using a named route):

map.openid "login", 
  :controller => "login" , 
  :requirements => { :method => :get }

Next, you have to reference such a named route in the root_url method of the login controller, as follows:

def root_url

This guarantees to the OpenID provider that the URL the user will be redirected to after the login belongs to the same domain (trusted root) as that from which the login request originated.

After this last step, your application now accepts only properly authenticated and identified users. You can use the additional information to improve the user experience. For example, you can enable your web site to pre-enter the user's name and email address when he creates a new to-do item. Open again the todos_controller.rb file and modify the new method as follows:

def new
  @todo = Todo.new
  @todo.person = session[:user_id].nickname
  @todo.email = session[:user_id].email

  respond_to do |format|
    format.html # new.html.erb
    format.xml  { render :xml => @todo }

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date