Re: Restful Authentication with all the bells and whistles (new 9/05/08)

I have a question in regads to the "password_required?" in the User model...

I thought this was to allow for the ability to create Users without a password, therefore giving them a passive state (using the stateful option and acts_as_state_machine plugin)... Am i wrong?

As of now, it seems the validation for the password still will prevent that from happening from sign_up (which i'm not to concerned about).

What I'm trying to do is, utilie a invite model.... that will create a User record, and send an invitation to another's email, and give i a passive state...

So when they do signup, i'd have the controller look for the email and check if it's passive... if so, then just update the info, and make it pending, send activation code....

I'm still trying to wrap my head around Rails a bit, but is it setup that way now? Or will I have to modify it to fit what i'm trying to do?

Last edited by Omarvelous (2008-02-16 19:16:36)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

RandyInLA wrote:

Thanks for that Jason!  It works perfectly.
 
Do you think it would be worth it to change the way the current_user method is configured to create a current_user object with role type 'visitor' instead of nil when someone isn't logged id? 
Then we could go back to just <% if current_user.has_role?('administrator') %>

What type of site is it? If every visitor is a user and required to login to view content then it seems like that would work.

WorkingWithRails.com
Person.recommend(jason_deppen)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

jdeppen wrote:

What type of site is it? If every visitor is a user and required to login to view content then it seems like that would work.

It's for a relationship therapist's site.  Most people will already be going to him as a counselor and will be going straight for the sign up page to create their free account.  Others who stumble upon the site will spend some time reading up on his particular counseling process and either signup or go away.

My reason for a 'visitor' role is simply to avoid current_user being nil.  Can't I have code that checks if someone is logged in, then create a session for them with a role as visitor if not?  Since most of the before filters only check the role of a person and not the logged in state, it just seemed the logical thing to do.  I'm not sure if I'd be opening up a whole can of worms or not.  That's what web browsers do, no?  Everyone who hits a site, logged in or not, is hitting the server as www_user of some sort and not as a nil entity, so why not in a rails app as well?  There must be a reason for web servers doing that...as long as a visitor is www_user, then server permissions can make sure they see/run only what is allowed.

Last edited by RandyInLA (2008-02-16 23:07:15)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

hi activefx,

this is really a very nice job, you've done on restfilling the authentication and everything!

thank you for sharing

/Walther

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

I am a complete RoR newbie and it is so nice to find a tutorial that actually works on Rails 2.0.x


I feel like I am so close to getting this up and running.  I followed the whole tutorial but for some reason it seems that my methods in my lib/authenticated_system.rb can not be seen.


Here are some error examples of what I am receiving.

http://justicefactory.com/logintest/public/users
http://justicefactory.com/logintest/public/login
http://justicefactory.com/logintest/public/roles


It's hard for me to troubleshoot being such a newb.  I am pretty sure there are not any typos left.  (I have already spent 3 hours hunting typos) tongue

THANKS so much for any help.


UPDATE:  I got it all working!  This is an amazing tutorial, I am so thankful!!!!

Last edited by theturnmaster (2008-02-17 04:00:15)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

RandyInLA wrote:
jdeppen wrote:

What type of site is it? If every visitor is a user and required to login to view content then it seems like that would work.

It's for a relationship therapist's site.  Most people will already be going to him as a counselor and will be going straight for the sign up page to create their free account.  Others who stumble upon the site will spend some time reading up on his particular counseling process and either signup or go away.

My reason for a 'visitor' role is simply to avoid current_user being nil.  Can't I have code that checks if someone is logged in, then create a session for them with a role as visitor if not?  Since most of the before filters only check the role of a person and not the logged in state, it just seemed the logical thing to do.  I'm not sure if I'd be opening up a whole can of worms or not.  That's what web browsers do, no?  Everyone who hits a site, logged in or not, is hitting the server as www_user of some sort and not as a nil entity, so why not in a rails app as well?  There must be a reason for web servers doing that...as long as a visitor is www_user, then server permissions can make sure they see/run only what is allowed.

I think you might be making more work for yourself than you need to by going that route.
My navigation changes if I have a logged in user. The user's role determines the navigation he/she sees. If there is no current_user (i.e. nil) then that visitor sees the public (default) navigation. An "administrator" sees even more navigation.

This is how I would probably do it to start and then I might move it into a helper later to DRY it up. I hope this helps you.

<% if logged_in? && current_user.has_role?('administrator') -%>
  #in-place text editor
<% else -%>
  #plain text
<% end -%>

Last edited by jdeppen (2008-02-17 00:45:16)

WorkingWithRails.com
Person.recommend(jason_deppen)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Thanks again Jason.  That's exactly how I have my code at the moment.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

How can I figure out why it's not sending activation emails?  The log file doesn't seem to have any errors and I'm using Dreamhost so the setup is pretty much exactly the same.  I wouldn't think so, but does it matter that I'm trying to run this on my localhost?  Thanks!

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

I seem to get an error when using db:migrate..
rake aborted!
undefined method `role=' for #<Permission:0xb786c538>
Seems it doesn't like the role section, any idea why this would be occuring?

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

@erikb: Do the reset password or forgot password emails work? I didn't have any problem sending from localhost through Dreamhost.

@haill: Did you create the role model before running the migration? Is the role migration running before the permission migration? Can you run the migration with --trace and post the error?

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

@nicolash:

With this code:

  def self.find_and_activate!(activation_code)
    raise ArgumentError if activation_code.nil?
    user = find_by_activation_code(activation_code)
    raise ActivationCodeNotFound if !user
    raise AlreadyActivated.new(user) if user.active?
    user.send(:activate!)
    user
  end

ArgumentError is called if the activation code in the url is non-existant, while AlreadyActivated is called if the user has been located in the database and the field for the activation code is blank. The only scenario that could occur under is if the user has already activated their account.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

slightly off topic but is anyone having "Install the ruby-openid gem to enable OpenID support" issues even though the gem is installed? thanks

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

A comment by Fulvio pointed out that the message for a failed login is not very descriptive. I have updated the tutorial to fix this problem.

The authenticate method in the User model has been changed from:

  def self.authenticate(login, password)
    u = find :first, :conditions => ['login = ? and enabled = ? and activated_at IS NOT NULL', login, true] # need to get the salt
    u && u.authenticated?(password) ? u : nil
  end

to:
  def self.authenticate(login, password)    
    u = find :first, :conditions => ['login = ?', login] # need to get the salt
    u && u.authenticated?(password) ? u : nil 
  end

And the password_authentication method in the Sessions Controller has been changed from:
  def password_authentication(login, password)
    self.current_user = User.authenticate(login, password)
    if logged_in?
      successful_login
    else
      failed_login("Your username or password was entered incorrectly, or if this is your first login attempt, you may not have activated your account.")
    end
  end

to:
  def password_authentication(login, password)
    user = User.authenticate(login, password)
    if user == nil
      failed_login("Your username or password is incorrect.")
    elsif user.activated_at.blank?   
      failed_login("Your account is not active, please check your email for the activation code.")
    elsif user.enabled == false
      failed_login("Your account has been disabled.")
    else
      self.current_user = user
      successful_login
    end
  end

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Very nice job on the tutorial activefx, it has helped get the wheels spinning for me.  For anyone interested in seeing another implementation of restful_authentication with all the bells and whistles, I starting writing about it at http://morebs.com/2008/02/20/my-take-on … tication/.  It is based off of this and still a work in progress.

Last edited by morebs (2008-02-21 00:54:54)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

The open_id_authentication plugin is being patched to work with verson 2+ of the ruby-openid gem. Therefore, I have added this section on openid authentication to the tutorial:


To get started, install the latest version of the openid gem:

# Installs ruby-openid-2.0.4
gem install ruby-openid

To install the plugin, you can use the git repository at http://github.com/josh/open_id_authenti … _openid_2.
If you don't have or can't use git, you can download a rar file of it
from http://rapidshare.com/files/93608377/op … .rar.html. Unrar it and copy the folder into
vendor/plugins. I tried to put it on rubyforge so there would be SVN access but it takes 72 hours.

Run the migration for the open id authentication tables:

rake open_id_authentication:db:create

Open the migration file XXX_add_open_id_authentication, and add a column for the OpenID url in your users table:
add_column :users, :identity_url, :string

So that your migration file now looks like:
class AddOpenIdAuthenticationTables < ActiveRecord::Migration
  def self.up
    create_table :open_id_authentication_associations, :force => true do |t|
      t.integer :issued, :lifetime
      t.string :handle, :assoc_type
      t.binary :server_url, :secret
    end

    create_table :open_id_authentication_nonces, :force => true do |t|
      t.integer :timestamp, :null => false
      t.string :server_url, :null => true
      t.string :salt, :null => false
    end
   
     add_column :users, :identity_url, :string
  end
 


  def self.down
    remove_column :users, :identity_url
    drop_table :open_id_authentication_associations
    drop_table :open_id_authentication_nonces
  end
end


Run the migration:
rake db:migrate

Add the route for OpenID authentication to your routes file:
map.open_id_complete 'session', :controller => "sessions", :action => "create", 
                                :requirements => { :method => :get }

so that your routes.rb file looks like:
ActionController::Routing::Routes.draw do |map|
  map.root :controller => "pages", :action => "index"
  map.signup '/signup', :controller => 'users', :action => 'new'
  map.login '/login', :controller => 'sessions', :action => 'new'
  map.logout '/logout', :controller => 'sessions', :action => 'destroy'
  map.activate '/activate/:id', :controller => 'accounts', :action => 'show'
  map.forgot_password '/forgot_password', :controller => 'passwords', :action => 'new'
  map.reset_password '/reset_password/:id', :controller => 'passwords', :action => 'edit'
  map.change_password '/change_password', :controller => 'accounts', :action => 'edit'
  map.open_id_complete 'session', :controller => "sessions", :action => "create", :requirements => { :method => :get }

  # See how all your routes lay out with "rake routes"
  map.resources :pages

  map.resources :users, :member => { :enable => :put } do |users|
      users.resource :account
      users.resources :roles
  end
 
  map.resource :session
  map.resource :password 
 
  # Install the default routes as the lowest priority.
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end


Modify the attr_accesible line in your User model so that your users can set and change their OpenID:
attr_accessible :login, :email, :password, :password_confirmation, :identity_url

You also need to modify the password_required? method in the User model:
  def password_required?
    not_openid? && (crypted_password.blank? || !password.blank?)
  end

and add below it:
  def not_openid?
    identity_url.blank?
  end

Edit the New sessions view at sessions/new.html.erb to include an option for an OpenID login:
<h2>Login with User ID and Password:</h2>
<% form_tag 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' %><br />
<label for="remember_me">Remember me:</label>
<%= check_box_tag 'remember_me' %></p> 


<p><br /><label for="openid_url">Optional - use your OpenID url:</label><br />
<%= text_field_tag "openid_url" %></p>

<p><%= submit_tag 'Log In' %> or <%= link_to "Sign Up", new_user_path %></p>
<% end -%>


If you want the OpenID logo used in this form field, add the following code to your CSS file:
input#openid_url {
   background: url(http://openid.net/login-bg.gif) no-repeat;
   background-color: #fff;
   background-position: 0 50%;
   color: #000;
   padding-left: 18px;
}

Next, modify the Sessions Controller to include OpenID authentication. Here we have added if using_open_id? and the methods open_id_authentication and create_open_id_user:
class SessionsController < ApplicationController
  layout 'application'
  before_filter :login_required, :only => :destroy
  before_filter :not_logged_in_required, :only => [:new, :create]
 
  # render new.rhtml
  def new
  end

  def create
    if using_open_id?
      open_id_authentication(params[:openid_url])
    else 
      password_authentication(params[:login], params[:password])
    end 
  end

  def destroy
    self.current_user.forget_me if logged_in?
    cookies.delete :auth_token
    reset_session
    flash[:notice] = "You have been logged out."
    redirect_to login_path
    #redirect_back_or_default('/')
  end
 
  protected
 
  def open_id_authentication(openid_url)
    authenticate_with_open_id(openid_url, :required => [:nickname, :email]) do |result, identity_url, registration|
      if result.successful?
        @user = User.find_or_initialize_by_identity_url(identity_url)
        if @user.new_record?
          if !registration['nickname'].blank? && !registration['email'].blank?
            @user.login = registration['nickname']
            @user.email = registration['email']
            create_open_id_user(@user)
          else
            flash[:error] = "Your persona must include at a minimum a nickname
                             and valid email address to use OpenID on this site."
            render :action => 'new'
          end
        else
          if @user.activated_at.blank?   
            failed_login("Your account is not active, please check your email for the activation code.")
          elsif @user.enabled == false
            failed_login("Your account has been disabled.")
          else
            self.current_user = @user
            successful_login
          end       
        end
      else
        failed_login result.message
      end
    end
  end
 
  def create_open_id_user(user)
    user.save!
    flash[:notice] = "Thanks for signing up! Please check your email to activate your account before logging in."
    redirect_to login_path
  rescue ActiveRecord::RecordInvalid
    flash[:error] = "Someone has signed up with that nickname or email address. Please create
                             another persona for this site."
    render :action => 'new'
  end   

  def password_authentication(login, password)
    user = User.authenticate(login, password)
    if user == nil
      failed_login("Your username or password is incorrect.")
    elsif user.activated_at.blank?   
      failed_login("Your account is not active, please check your email for the activation code.")
    elsif user.enabled == false
      failed_login("Your account has been disabled.")
    else
      self.current_user = user
      successful_login
    end
  end
 
  private
 
  def failed_login(message)
    flash.now[:error] = message
    render :action => 'new'
  end
 
  def successful_login
    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
      flash[:notice] = "Logged in successfully"
      return_to = session[:return_to]
      if return_to.nil?
        redirect_to user_path(self.current_user)
      else
          redirect_to return_to
      end
  end

end


Even though nickname and email are listed as required openid fields, the application was still allowing a new user to be created without them, so I had to !registration['nickname'].blank? && !registration['email'].blank? validation to make sure they were actually present. If anyone knows any more about this let me know. Also, I couldn't get the remember me checkbox to work with openid authentication because of the redirect to your openid provider. Please let me know if anyone has a fix for this as well.

References:
http://railscasts.com/episodes/68
http://github.com/josh/open_id_authenti … y_openid_2
http://www.bencurtis.com/archives/2007/ … enticated/

Last edited by activefx (2008-02-23 05:11:42)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

I'm getting a weird error - undefined method `helper_method' for Object:Class

lib/authenticated_system.rb:141:in `send'
lib/authenticated_system.rb:141:in `included'
app/controllers/application.rb:4:in `include'
app/controllers/application.rb:4

I'm using rails 2.0.2 on OSX, with the stock ruby 1.8.6.

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Thanks for the update, this is exciting.
I've now got it working with OpenID and even added a little flavor. I have a permanent AJAX login bar under the header (updates navigation depending on the user) with a toggle OpenID (link_to_function).
Thanks again.

WorkingWithRails.com
Person.recommend(jason_deppen)

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

Great tutorial!! I'm only having one minor problem. Every activated account generates two confirmation emails. All other emails are working properly. I've tried starting a new project and using the debugger with no luck. Can anyone suggest how I can track down the cause of the problem?

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

I've gone through the tutorial twice, but keep running into an error when testing the app.  If the unordered list with the authentication menu is placed in my site wide layout, it says:

 NoMethodError in Pages#index
Showing layouts/pages.html.erb where line #13 raised:
undefined method `logged_in?' for #<ActionView::Base:0x27b05a0>

and if I go to /session/new, it says:

 NoMethodError in SessionsController#new
undefined method `not_logged_in_required' for #<SessionsController:0x2723c68>

Does anyone know what might be causing this? Thanks!

Re: Restful Authentication with all the bells and whistles (new 9/05/08)

@starflyer Make sure you have "include AuthenticatedSystem" in your application.rb