User Authentication


#1

I’m trying to create a simple authentication system but am failing
miserably. I’m (sort of) following the “Logging In” chapter of the Agile
book. Ideally, any access to http://example.com/publish (and any of its
subdirectories) should redirect to http:/example.com/publish/login when
there is no valid session user_id.

Code so far:


controller

class PublishController < ApplicationController

before_filter :authenticate, :except => [:login, :login_user]

def login
if request.post?
logged_in_user = @user.attempt_login
if logged_in_user
session[:user_id] = logged_in_user.id
redirect_to(:action => ‘index’)
else
flash[:notice] = ‘Invalid username or password. Please try
again’

    end
end

end

def self.authenticate
unless session[:user_id]
redirect_to :action => ‘login’
end
end

end

#model
class User < ActiveRecord::Base

require “digest/sha1”

attr_accessor :password
attr_accessible :username, :password, :first_name, :last_name, :email

validates_uniqueness_of :username
validates_presence_of :username, :first_name, :last_name, :email
validates_length_of :password, :within => 5…40, :on => :create

def before_create
self.sha_password = User.encrypt(self.password)
end

def after_create
@password = nil
end

private
def self.encrypt(password)
Digest::SHA1.hexdigest(password)
end

def self.login_user(username, password)
encrypted_password = encrypt(password || “”)
find(:first, :conditions => [“username = ? and sha_password = ?”,
username, encrypted_password])
end

def attempt_login
User.login_user(self.username, self.password)
end

end


If I browse to, say, http://example.com/publish/user/list I get a
NoMethodError in Publish#user:

undefined method `authenticate’ for #PublishController:0x387ab58W

When I browse to http://example.com/publish/login/ I see the login form
but
upon submit I get another NoMethodError in Publish#login:

You have a nil object when you didn’t expect it!
The error occured while evaluating nil.attempt_login

I’m totally stumped and appreciate any help.

Greg


#2

Greg MacGregor wrote:

I’m trying to create a simple authentication system but am failing
miserably. I’m (sort of) following the “Logging In” chapter of the Agile
book. Ideally, any access to http://example.com/publish (and any of its
subdirectories) should redirect to http:/example.com/publish/login when
there is no valid session user_id.

Greg MacGregor wrote:

I’m trying to create a simple authentication system but am failing
miserably. I’m (sort of) following the “Logging In” chapter of the Agile
book. Ideally, any access to http://example.com/publish (and any of its
subdirectories) should redirect to http:/example.com/publish/login when
there is no valid session user_id.

Are you sure you don’t want to save a lot of time and use one of the
pre-written packages that are available? Salted, for example:

http://wiki.rubyonrails.com/rails/pages/SaltedHashLoginGenerator

I am a fellow Rails newbie, and I had no problem getting Salted up and
running. And it’s really easy to modify. You might also want to look
here, for user authorization:

http://d-haven.org/modules/news/article.php?storyid=28

I haven’t tried that yet, but I’m going to set it up tomorrow, and I’m
thinking it will be up and running in 1-2 hours.

One of the reasons Rails is so awesome is that there is SO much existing
code out there to use. And it’s so easy to modify to do exactly what you
want to do.

BUT… if you do want to write your own User system, I’m sure there’s a
lot of folks here who can help you. You wouldn’t want to be taking
Rails or Ruby coding advice from me just yet…

Micah


#3

Micah B. wrote:

Are you sure you don’t want to save a lot of time and use one of the
pre-written packages that are available?

… building it myself (and running into errors such as this) will help
me to better understand how the controller and model interact with one
another. I appreciate the links, Micah!

Greg


#4

On 3/1/06, Greg MacGregor removed_email_address@domain.invalid wrote:

      redirect_to(:action => 'index')
    else
      flash[:notice] = 'Invalid username or password. Please try again'
    end
end

end

def self.authenticate

This needs to be a regular method (what’s the correct terminology
here?), not a class method.
You want:

  def authenticate

And I’d put the authenticate method in the Application controller,
that way other controllers can access it (note: if you do that, you’ll
need to change the below redirect to also include the controller.

Joe


#5

Hi Greg,

comments inline:

On 1-Mar-06, at 8:37 PM, Greg MacGregor wrote:

I’m trying to create a simple authentication system but am failing
miserably. I’m (sort of) following the “Logging In” chapter of the
Agile book. Ideally, any access to http://example.com/publish (and
any of its subdirectories) should redirect to http:/example.com/
publish/login when there is no valid session user_id.

Okay, it’s the “sort of” that’s biting you in the rear right now.
I’ll point out why you’re getting the errors but I advise you to go
back and work through the Logging In chapter again, step by step.

more below:

if request.post?

One of your errors is that you’re trying to call (effectively)
“nil.attempt_login”. The offending line is above, where you call
@user.attempt_login. The problem is, you’re not assigning a value to
@user so it’s ‘nil’.

If I look at your User model below, I’d guess you really want to say:

logged_in_user = User.login_user(params[:username], params[:password])

of course, this assumes you’re actually passing username and password
as POST params.

def self.authenticate
unless session[:user_id]
redirect_to :action => ‘login’
end
end

The authenticate method above has “self.” in front of it which makes
it a class method. i.e. you can only call it as
PublishController.authenticate

However, your filters are expected to be instance methods of the
controller so ditch the ‘self.’ and that will eliminate your
"undefined method authenticate" error.

HTH,
Trevor

Trevor S.
http://somethinglearned.com


#6

On 3/1/06, Greg removed_email_address@domain.invalid wrote:

    if logged_in_user
unless session[:user_id]

There is indeed a list action in the user controller, so how come the
redirect doesn’t work?

Thanks again for the help.

Greg

Is the User controller defined like:
class Publish::AdminController < ApplicationController
?

What exactly doesn’t work with the redirection?


#7

Thanks Trevor. Before you posted I made these changes:

class PublishController < ApplicationController

before_filter :authenticate

def login
if request.post?
@user = User.new(params[:user])
logged_in_user = @user.attempt_login
if logged_in_user
session[:user_id] = logged_in_user.id
redirect_to(:controller => ‘/publish/user’, :action => ‘list’)
else
flash[:notice] = ‘Invalid username or password. Please try
again’
end
end
end

def authenticate
unless session[:user_id]
redirect_to :action => ‘login’
end
end

end


and i added this to my model:

public
def attempt_login
User.login_user(self.username, self.password)
end

(note public)

everything seems to work now but I have another problem. After
successful login I want to point the user to
example.com/publish/user/list. I’ve referred to this as

redirect_to(:controller => ‘/publish/user’, :action => ‘list’)

above. My app looks like

app
+controllers
-application_controller.rb
-publish_controller.rb
+publish
-user_controller.rb

There is indeed a list action in the user controller, so how come the
redirect doesn’t work?

Thanks again for the help.

Greg


#8

Greg,
Like you, when I first started with Rails I used the authentication
sample from the Agile book. But I quickly dicovered that it was too
limited, even for the hobby site I was building.

I recommend looking at the acts_as_authenticated plugin. It’s easy to
set up, and has convenience methods for handy things like displaying the
name of the user logged on (e.g. current_user.login). Or, “logged_in?”,
a simple test to determine if someone is logged in. This is handy if you
want to display special menus to users who are logged in vs. the general
public. Of course, that just scratches the surface. It also integrates
ActionMailer for emailing, etc.

Of course, you’re free to code this stuff yourself, but I decided that
using an available plugin for the drudgery of authentication left me
with more time for writing my “actual” application.

FYI, There’s even a separate, new ACL system written to complement the
acts_as_authenticated plugin (I haven’t tried it yet). So you can assign
roles/permissions to certain users.

Good luck!
-Chris


#9

Joe Van D. wrote:

Is the User controller defined like:
class Publish::AdminController < ApplicationController
?

What exactly doesn’t work with the redirection?

Given my structure above, the User controller is defined like this:
class Publish::UserController < ActionController … or

-app
+controllers
-publish_controller.rb
+publish
-user_controller.rb

Like I said, there is a list action in the user_controller that I know
works. After login I am redirected to:
http://example.com/publish/user/list and get the error: “no action for
user#list” or something to that effect. I don’t understand why that is
happening if the action is indeed there?!

I think that I may use a pre-fab authentication controller after all.
This is giving me a headache. Thanks again to all of you for the help.

Greg