sf-wdi-31 / rails-auth

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Auth in Rails

Why is this important?

This workshop is important because:

Many apps track user data and allow users to sign up, log in, and log out. Today we'll look at a strategy to implement these features.

What are the objectives?

After this workshop, developers will be able to:

  • Build routes, controllers, and views for user sign up, log in, and log out.
  • Implement a User model that securely stores passwords with has_secure_password.
  • Differentiate authentication and authorization.

Where should we be now?

Before this workshop, developers should already be able to:

  • Describe the role of routes, controllers, views, and models.

Aside: Devise

Some of you have heard of Devise, a gem that will handle auth for you. That's cool and all, but they recommend that if you're new to rails:

If you are building your first Rails application, we recommend you do not use Devise. Devise requires a good understanding of the Rails Framework. In such cases, we advise you to start a simple authentication system from scratch. ... <a href="https://github.com/plataformatec/devise#starting-with-rails"\>[docs]

That's what we're going to do!

What is "auth"?

People generally mean two different things when they say "auth":

  1. Authentication - confirming that a user is who they say they are.
  2. Authorization - determining whether a user is permitted to perform an action.

Auth Strategy: Sessions

HTTP is designed to be stateless. We could have users submit login details for every request, but that would get really tedious really quickly.

We'll store information about the logged in user in a session. (See Rails Security Guide Sessions section for a refresher.)

Below is a summary of what we will use:

Routes

  get '/login', to: 'sessions#new'
  post '/login', to: 'sessions#create'
  get '/logout', to: 'sessions#destroy'

  get '/signup', to: 'users#new'
  post '/users', to: 'users#create'

Models

We'll need one model: Users. We'll add name, email and password_digest attributes.

# Table name: users
#
#  id              :integer          not null, primary key
#  name            :string
#  email           :string
#  password_digest :string

Handling Passwords

We'll use the gem bcrypt so that we never store user passwords as plain text. BCrypt is a hashing algorithm, and people like it because you can customize how secure a job it does based on a "cost" factor. The bcrypt gem will let us simply and easily hash and salt our passwords to obscure them with the BCrypt algorithm. Let's try it out.

Independent Practice: What does bcrypt do?

  1. In your Terminal, install the bcrypt gem: gem install bcrypt.

  2. Start up irb or pry, and try out the console commands below:

  2.3.0 :001 > require 'bcrypt'
   => true
  2.3.0 :002 > my_password = BCrypt::Password.create('secret')
   => "$2a$10$7z71iDWik1luWbDR.2uC2ObB5fPT5jSbWFfsp3YGnsZrcRIHIkjne"
  2.3.0 :003 > my_password == "secret"
   => true
  2.3.0 :004 > my_password == "oops"
   => false
  1. Play around with different values. What happens if you run "secret" through the bcrypt gem again? Do you get the same output?

has_secure_password

In Rails, the bcrypt gem gives us an intensely easy-to-use has_secure_password method that we can add to a class we want to authenticate with.

class User < ActiveRecord::Base
  # first make sure bcrypt is included in Gemfile
  has_secure_password

end

This will make sure the passwords are NOT stored as plain text in the database. It will store the password in the database under the column password_digest.

It also adds an authenticate method which can check a given password against the hashed and salted version in the database:

  # some controller somewhere
  user = User.find_by_email(params[:email])
  if user && user.authenticate(params[:password])
    # ...

Note that we could use bcrypt to write these methods ourselves - in fact, that's exactly what the source code does! See has_secure_password's documentation and source code.

Session Store for User ID

Whenever we authenticate a user, we will set the session hash's user_id to the user.id:

session[:user_id] = user.id

You'll want to do this at signup and log in.

Check for understanding: How would we use the session hash to log a user out?

Authorization

You've seen authentication, but for many apps you'll still want to add authorization.

  • We suggest making a current_user method in your ApplicationController or in a helper file so that each controller doesn't need to implement a way to check which user is logged in.
  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  helper_method :current_user
  • Then, in each of your controllers, check whether the current_user should be allowed to do the action they're trying to. You can use before_action to keep this code DRYer.

  • Consider adding conditionals to your views. For example, don't show links to users who can't use them.

Conclusion

That's it, authentication and authorization in a nutshell. Not too bad, right?

For an example, see a WDI lab solution implementing auth in Rails 4.

About