This document provides an overview for how to set up authentication and includes code for permitting login and logout functions. It is based on the method presented in the Rails 3.0 Agile Web Development book.
The document is written for Rails 3.0, but it should also work with Rails 3.1. Note, however, that Rails 3.1 provides a helper method (has_secure_password), which automatically gives the User model password attributes and an authenticate method.
A user model is first created by generating a model or scaffold with the following physical attributes:
This can be created with the following command:
rails g scaffold User name:string hashed_password:string salt:string
Additional attributes can be added for describing the user.
Don't forget the command rake db:migrate so that the table is actually created.
After the users table is created, code needs to be added to the user model (user.rb in the models folder) so that the model has virtual attributes (password and password_confirmation) and an authentication method. To add the needed model code, simply copy the model code to the user.rb file, replacing all of the previous contents. You may want to add additional validations and references depending on the needs of your application.
The rails console provides a good interface for learning how to use the user model and its methods. Here are examples that can be tried out on the console.
u = User.new u.name = 'sam' u.password = 'secret' u.password_confirmation = 'secret' u.save
Note that the object won't save if the password doesn't match the password_confirmation. Try it!
In its current form, the scaffold views doesn't allow you to add new users. However, it can work after it is edited with the virtual password attributes.
The authenticate class method determines whether a user name and password is correct:
User.authenticate('sam', 'secret')
If successful, authenticate returns the user object. Otherwise, it returns nil.
To allow a user to login and logout, a sessions controller can be created with the needed actions:
rails g controller sessions new create destroy
While the generate commands creates routes for all three actions, the routes need to be edited in the routes.rb file (config folder) so that create uses the post method and destroy is categorized as a delete operation:
controller :sessions do get 'login' => :new post 'login' => :create delete 'logout' => :destroy end
Don't forget to remove the old routes! These new routes provide meaningful names for the URL that accesses them. For example, localhost:3000/login will be routed to the new action in the sessions controller. You can test whether the routes are correctly in place by running the command rake routes.
In the sessions controller, only the create and destroy actions require code:
def create if user = User.authenticate(params[:name], params[:password]) session[:user_id] = user.id redirect_to root_path else @alert = "Invalid user/password combination" render 'new' end end def destroy session[:user_id] = nil redirect_to root_path, :notice => "Logged out" end
The create action checks if the user submitted a valid login name and password using the authenticate method. If so, the user_id is set in the session hash. Then, control is redirected to the specified path (you may want it to go somewhere other than the root path). If the name and password is not correct, the controller renders the form again with the alert message.
The destroy action just sets the user_id in the session hash to nil before redirecting to the specified path.
Only the new action requires any view code (new.html.erb):
<% if @alert %> <p><%= @alert %></p> <% end %> <%= form_tag login_path do %> <p> <label for="name">Name:</label> <%= text_field_tag :name, params[:name] %> </p> <p> <label for="password">Password:</label> <%= password_field_tag :password, params[:password] %> </p> <%= submit_tag 'Login' %> <% end %>
Specifying the login_path for the form_tag isn't strictly necessary since the form is automatically submitted to the same path as the form but using the post method.
To logout, the user can click on a Logout link created using the following code:
<%= link_to 'Logout', logout_path, :method => :delete %>
A controller can test whether a user is logged in by checking whether session[:user_id] is not nil. Rails filters are typically used for checking login status and limiting access. These details are covered on pages 197 - 200 in the Agile Web Development book.