Quantcast
Viewing all articles
Browse latest Browse all 10

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. Image may be NSFW.
Clik here to view.
:)

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
  end
 
  # Added after restful_authentication generator, using code from http://www.loudthinking.com/arc/000604.html
  # 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'])
          successful_login
        else
          failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
        end
      else
        failed_login "Unknown error logging in #{identity_url}"
      end
    end
  end
 
  # 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"
        else
          failed_login "Unknown error in openid begin for #{identity_url}"        
        end
      end
    elsif params[:login]
      password_authentication(params[:login], params[:password])
    else
      failed_login "No valid credentials passed to create authenticated session"
    end
  end
 
  def destroy
    logger.info "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."
    redirect_back_or_default(calls_path())
  end
 
protected
 
  def password_authentication(login, password)
    if self.current_user = User.authenticate(params[:login], params[:password])
      successful_login
    else
      failed_login("Invalid login or password")
    end
  end
 
private
 
  def successful_login
    logger.info "sucessful login for #{self.current_user.inspect}"
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
    end
    redirect_back_or_default("/")
    flash[:notice] = "#{self.current_user.login} Logged in successfully"
  end
 
  def failed_login(message)
    logger.info 'Login failed. ' + message
    flash[:notice] = message
    redirect_to new_authenticated_session_path()
  end
 
end

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>
 
<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 %>
</div>

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.


Viewing all articles
Browse latest Browse all 10

Trending Articles