open_id_authentication (part 2)

In part 1, we looked at some changes to DHH’s OpenID plugin for rails. Now we look at the app’s authenticated sessions controller, which uses that library code. The goals were:

  • Support both OpenID and password authentication
  • Support all the usual login goodies, including before_filter :login_required, remember me functionality (with cookies), HTTP basic authentication, etc.
  • Get this by integrating the restful_authentication plugin/generation and open_id_authentication plugin

First, I installed both plugins and ran the restful_authentication generator

./script/generate authenticated user authenticated_sessions

I chose the name “authenticated_sessions” to distinguish from the built-in (non-authenticated) Rails sessions functionality.

routes.rb adds

  map.resource :authenticated_session, :member => { :complete => :get }
  map.resources :users

Then it’s a matter of some substantial edits to the generated app/controllers/authenticated_session_controller.rb Here’s where it would be nice to have a combined generator like Eastmedia’s. If you’re just trying to get an app and and running, you don’t want to be reading a blog posting like this. :)

The smartest design decisions in the file below (which all came from DHH’s code), are having centralized successful_login(), failed_login(), and destroy() functions, which are common across the otherwise separate openid/password authentication paths.

# This controller handles the login/logout function of the site. 
# File created with restful_authentication generator 
class AuthenticatedSessionController < ApplicationController
  # render new.rhtml
  def new
  # Added after restful_authentication generator, using code from
  # Complete arrives to us via a browser redirect from the OpenID provider, which happens after create/begin.
  # Obviously only called in the OpenID path through authentication.
  def complete
    complete_open_id_authentication(params[:openid_url]) do |result, identity_url, sreg|
      case result
      when :canceled
        failed_login "OpenID verification was canceled"
      when :failed
        failed_login "Sorry, the OpenID verification failed"
      when :successful
        if self.current_user = User.find_by_openid_url(identity_url) || User.create(:openid_url => identity_url, :login => sreg['nickname'], :email => sreg['email'])
          failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
        failed_login "Unknown error logging in #{identity_url}"
  # Handle the creation of a new authenticated session, OpenID or password authentication path
  def create
    if using_open_id?
      begin_open_id_authentication(params[:openid_url], :required => "nickname, email") do |result, identity_url|
        case result
        when :missing
          failed_login "Sorry, the OpenID server couldn’t be found"
          failed_login "Unknown error in openid begin for #{identity_url}"        
    elsif params[:login]
      password_authentication(params[:login], params[:password])
      failed_login "No valid credentials passed to create authenticated session"
  def destroy "logging out #{self.current_user.inspect}"
    self.current_user.forget_me if logged_in?
    cookies.delete :auth_token
    @session[:user] = nil
    flash[:notice] = "You have been logged out."
  def password_authentication(login, password)
    if self.current_user = User.authenticate(params[:login], params[:password])
      failed_login("Invalid login or password")
  def successful_login "sucessful login for #{self.current_user.inspect}"
    if params[:remember_me] == "1"
      cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
    flash[:notice] = "#{self.current_user.login} Logged in successfully"
  def failed_login(message) 'Login failed. ' + message
    flash[:notice] = message
    redirect_to new_authenticated_session_path()

Lastly, views/authenticated_session/new.rhtml is where the initial form is presented when users are redirected from an action that requires authentication. I chose to put the password and openid authentication forms on the same page, but within their own divs with IDs and a common rounding class, so the styles can be tuned in the .css file to make it look nice.

<div id='password-login' class='rounded'>
	<% form_tag authenticated_session_path do -%>
	<p><label for="login">Login</label><br/>
	<%= text_field_tag 'login' %></p>
	<p><label for="password">Password</label><br/>
	<%= password_field_tag 'password' %></p>
	<p><label for="remember_me">Remember Me</label><%= check_box_tag 'remember_me' %></p>
	<p><%= submit_tag 'Log in' %></p>
	<% end -%>
<div id='openid-login' class='rounded'>
	<% form_tag authenticated_session_path do %>
	  <p><label for="openid_url">OpenID</label><%= text_field_tag 'openid_url' %></p>
	  <p><label for="remember_me">Remember Me</label><%= check_box_tag 'remember_me' %></p>
	  <%= submit_tag 'Login' %>
	<% end %>

You’ll notice that most of my postings and a lot of the plugins have headed in the direction of making authenticated sessions a REST resource, using the newer RESTful support in Rails. Reflecting back on it all, it isn’t the best conceptual or practical match. As long as the end-user functionality is the same (support for remember_me, HTTP basic authentication, etc.) — it seems more strightforward to implement authenticated sessions as a traditional controller, not a REST resource.

Comments (3) to “open_id_authentication (part 2)”

  1. I’m trying to get the :remember_me functionality to work with open_id_auth. Since it leaves to verify at the identity server, the remember_me param is wiped out when it gets back. What are your thoughts?

  2. Hi Ryan,

    You’re right, there appears to be an error in persisting the remember_me param from begin() through the redirects to complete(). In part one of the article (changes to DHH’s lib), there should be additional changes to add the remember_me param to the redirect URL (line 115, as I have the line numbers in the diffs in part 1 of the article). To make that happen, you’ll also have to add paramters to the routines there to pass down whether remember_me is set.

    Thanks for catching this error — let us know if you code up a solution that works!

  3. Thanks. this informations helps me a lot.
    by the way,
    “AuthenticatedSessionController” should be “AuthenticatedSession[s]Controller” and “@session[:user] = nil” in the destroy method should be “session[:user_id] = nil” I think.
    any way, thanks a lot.

Post a Comment
(Never published)