h3. There are a number of pages that cover the topic of authentication see [[Authentication]] for an overview.

The controller that needs protection:

<pre><code>
class \WeblogController < ActionController::Base
  before_filter :authenticate

  def index
    # show the secret stuff
  end

  protected
    def authenticate
      unless @session["person"]
        @session["return_to"] = @request.request_uri
        redirect_to :controller => "login"
        return false
      end
    end
end
</code></pre>

Comment: This page is a great help; however I found that I was getting session restore errors like this: "_ActionController::SessionRestoreError: Session contained objects where the class definition wasn't available. Remember to require classes for all objects kept in the session. The session has been deleted. (Original exception: undefined class/module User [ArgumentError])_"
and therefore had to specify *model :person* in my equivalent of this WeblogController to make it work. 
-- DjAdams

Comment: I had the same problem.  The webbrick will all of a sudden start throwing up 404s.  You can delete the ruby session to see pages again or just do what he says and add model :person.   I had to add that line to weblog_controller to get login to work, and logincontroller to get logout to work.
-- SM

The controller that does login:

<pre><code>class \LoginController < ActionController::Base
  def index
    # show login screen
  end
  
  def authenticate
    if @session["person"] = Person.authenticate(@params["name"], @params["password"])
      if @session["return_to"]
        redirect_to_path(@session["return_to"])
        @session["return_to"] = nil
      else
        redirect_to :controller => "weblog"
      end
    else
      flash["alert"] = "Login failed!"
      redirect_to :action => "index"
    end
  end

  def logout
    reset_session
    flash["alert"] = "Logged out"
    redirect_to :action => "index"
  end
end
</code></pre>

Comment: The "reset_session" call above seems to invalidate the following flash statement, so it doesn't take effect. Either say @session["person"] = nil, or drop the flash.
--[[Lars Pind]]

Comment: The protected call now requires you to make sure your other methods are public. Authentication works without the protected call on 0.9.5 for me.
--[[khudgins]]

The model that does authentication:

<pre><code>
class Person < ActiveRecord::Base
  def self.authenticate(name, password)
    find(:first,
      :conditions => [ "name = ? AND password =?", name, password ]
    )
  end
end
</code></pre>

Notice that in this example there is no password encryption used. However its very easy to add :

<pre><code>
require 'digest/sha1'

class User < ActiveRecord::Base

  def self.authenticate(email, password)
    find(:first, 
      :conditions => ["email = ? and password = ?", email, Digest::SHA1.hexdigest(password)]
    )
  end

  def password=(str)
    write_attribute("password", Digest::SHA1.hexdigest(str))
  end

  def password
    ""
  end
end
</code></pre>

Don't forget that your password fields have to take 40 char long strings for SHA1 and 32 char strings for MD5. MD5 was recently cracked ( by way of a collision ) so SHA1 is the thing to do now. 

bq. Although it is true that MD5 collisions were found (which is very different than a crack), it is also true that an explicit demonstration of MD5s insecurity failed. Blindly trusting, or  dismissing one technology or algorithm without a proper level of understanding can be dangerous to your security. Read up on "MD5 at Wikipedia":http://en.wikipedia.org/wiki/Md5#Security for an introduction.   
- LeeO

You can also do AuthenticationWithAbstractApplicationController

<hr />
_example_: keeping the above example in mind, the easiest way to do that would probably be something like this:
<pre>
<code>
  def authenticate
    if person = Person.authenticate(@params["name"], @params["password"])
    @session["person_id"] = person.id
    # rest of code #
</code>
</pre>
- jsdk

<hr />

sanitize

If you have a user object with a lot of associated objects (has_many :posts), the session can get _very_ large if you put the whole user object in it. I usually only save the user_id in the session and create the user object in a before_filter.

_Which means you add a round-trip to database_ *on each request* _for no apparent good reason. Better to keep associations out of user object, IMHO. --AlexeyVerkhovsky _ 

One additional DB request does rarely affect the performance (the time the database needs to process a simple primary key select is negligable anyway).  And why should I keep associations out of the user object if there _are_ associations in the data structure? Furthermore I don't want expired user data to continue living inside a session.
_That's an interestng point --Alex_
<hr />

Q: Does using MySQL SHA1 function causes plain-text passwords to show up in the errorlog?
A: Actually the plain-text password will only show up in the mysql query log. Activating a query log in a production enviroment could be considered a bad thing no matter what. The query log is written as the mysql user, so normal users won't be able to read this log, still not a very good idea though.

Q: The SQL shown allows SQL injection, doesn't it? 
A: No, there is no SQL injection because it is using a prepared query (the ? in the query). Using something like find_first "user=#{user}" would allow SQL injection.

bq. The queries on this page have been updated to make use of Rails automatic value sanitizing. You can read more about it on the "ActiveRecord API page, under 'Conditions'":http://rails.rubyonrails.com/classes/ActiveRecord/Base.html  
- LeeO

<hr/>

You might want to reconsider storing the straight SHA1-encoded passwords in your database. Few people pick good passwords, and thus if an attacker (or disgruntled employee) ever gets a copy of your database – something the daily news suggests is more common than we would like to hope – dictionary-based attacks are likely to reveal many of the encoded passwords. Further, because there is a one-to-one mapping from clear passwords to your encoded representation, an attacker can attack all accounts simultaneously and even use a precomputed dictionary.

To reduce the risk of these problems, I combine the password with a random salt, compute the digest of the combination, and then store both the salt and the digest in the database.  The following code shows one way to do it, using a Base64-encoded 48-bit salt:

<pre><code>
  def password=(pw)
    salt = [Array.new(6){rand(256).chr}.join].pack("m")[0..7]; # 2^48 combos
    # password_salt and password_sha1 are DB-backed AR attributes
    self.password_salt, self.password_sha1 =
      salt, Digest::SHA1.hexdigest(salt + pw)
  end

  def password_is?(pw)
    Digest::SHA1.hexdigest(self.password_salt + pw) == self.password_sha1
  end
</code></pre>

--[[Tom Moertel]]

<hr/>

What is the best please to use @session['user'].id to ensure that users are only editing records that they own? Something like:

Post.delete_all ['id=? AND user_id=?', @params['id'], @session['user'].id]

Or is there a better way?

category:Howto
