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.1 Agile Web Development book.
The document is written for Rails 3.1 and later. It uses the the helper method has_secure_password, which automatically gives the User model password attributes and an authenticate method.
User model
A user model is first created by generating a model or scaffold with the following physical attributes:
- name: string
- password_digest: string
This can be created with the following command:
rails g scaffold User name:string password_digest: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. With Rails 3.1, these virtual attributes are automatically created with the addition of this Ruby statement inside the user.rb file:
has_secure_password
This helper method requires the use of the bcrypt gem. To make sure this gem is included in your bundle, edit the Gemfile file in your main folder so that the following line no longer has the comment symbol (#) in front of it:
gem 'bcrypt-ruby', '~> 3.0.0'
Using the User model
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.
Creating a user
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.
Authenticating a user
The authenticate instance method determines whether a password is correct:
user = User.find_by_name('sam') user.authenticate('secret')
If successful, authenticate returns the user object. Otherwise, it returns false.
Creating the sessions controller
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 bundle exec rake routes.
In the sessions controller, only the create and destroy actions require code:
def create user = User.find_by_name(params[:name]) if user and user.authenticate(params[:password]) session[:user_id] = user.id redirect_to root_url else flash.now.alert = "Invalid name/password combination" render 'new' end end def destroy session[:user_id] = nil redirect_to root_url, :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 table. 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 table to nil before redirecting to the specified path.
Creating Views for the sessions controller
Only the new action requires any view code (new.html.erb):
<%= form_tag login_url 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_url, :method => :delete %>
Limiting Access
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.