Last Updated: February 25, 2016
·
5.438K
· maksoy

Rails Authenticity Token

hi,

I want to explan the Remember Token/Authenticity Token based on an example (Signin/Signout).
Ruby provides a module facility for packaging functions together and including them in multiple places, and that’s the plan for the authentication functions.
You could make an entirely new module for authentication, but the Sessions controller already comes equipped with a module, namely, SessionsHelper. Moreover, such helpers are automatically included in Rails views, so all we need to do to use the Sessions helper functions in controllers is to include the module into the Application controller

app/controllers/sessions_controller.rb

class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # Sign the user in and redirect to the user's show page.
    else
      flash.now[:error] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
  end
end

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

By default, all the helpers are available in the views but not in the controllers. You need the methods from the Sessions helper in both places, so we have to include it explicitly.

Because HTTP is a stateless protocol, web applications requiring user signin must implement a way to track each user’s progress from page to page. One technique for maintaining the user signin status is to use a traditional Rails session (via the special session function) to store a remember token equal to the user’s id:

session[:remember_token] = user.id

This session object makes the user id available from page to page by storing it in a cookie that expires upon browser close. On each page, the application could simply call

User.find(session[:remember_token])

to retrieve the user. Because of the way Rails handles sessions, this process is secure; if a malicious user tries to spoof the user id, Rails will detect a mismatch based on a special session id generated for each session.

For our application’s design choice, which involves persistent sessions—that is, signin status that lasts even after browser close—you need to use a permanent identifier for the signed-in user. To accomplish this, you’ll generate a unique, secure remember token for each user and store it as a permanent cookie rather than one that expires on browser close.

The remember token needs to be associated with a user and stored for future use, so we’ll add it as an attribute to the User model, as shown in Figure

Now you have to decide what to use as a remember token. There are many mostly equivalent possibilities—essentially, any large random string will do just fine, as long as it’s unique. The urlsafe_base64 method from the SecureRandom module in the Ruby standard library fits the bill it returns a random string of length 16 composed of the characters A–Z, a–z, 0–9, “-”, and “_” (for a total of 64 possibilities, thus “base64”). This means that the probability of two remember tokens colliding is a negligibly small 1/6416=2−96≈10−29.

Our plan is to store the base64 token on the browser, and then store an encrypted version in the database. You can then sign users in automatically by retrieving the token from the cookie, encrypting it, and then searching for a remember token matching the encrypted value. The reason for storing only encrypted tokens is so that, even if our entire database is compromised, the attacker still won’t be able to use the remember tokens to sign in. To make our remember token even more secure, we’ll plan to change it every time a user creates a new session, which means that any hijacked sessions—in which an attacker uses a stolen cookie to sign in as a particular user—will expire the next time a user signs in. (Session hijacking was widely publicized by the Firesheep application, which showed that remember tokens at many high-profile sites were visible when connected to public Wi-Fi networks. The solution is configuring the application to use SSL in production.

SampleApp::Application.configure do
  .
  .
  .
  # Force all access to the app over SSL, use Strict-Transport-Security,
  # and use secure cookies.
  config.force_ssl = true
  .
  .
  .
end

First, we add a callback method to create a remember token immediately before creating a new user in the database: app/models/user.rb

beforecreate :createremember_token

This code, called a method reference, arranges for Rails to look for a method called createremembertoken and run it before saving the user.

private

  def create_remember_token
    # Create the token.
  end

All methods defined in a class after private are automatically hidden, so that

$ rails console
>> User.first.create_remember_token

will raise a NoMethodError exception.

Finally, the createremembertoken method needs to assign to one of the user attributes, and in this context it is necessary to use the self keyword in front of remember_token:

def User.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def User.encrypt(token)
    Digest::SHA1.hexdigest(token.to_s)
  end

  private

    def create_remember_token
      self.remember_token = User.encrypt(User.new_remember_token)
    end

Because of the way Ruby handles assignments inside objects, without self the assignment would create a local variable called remembertoken, which isn’t what we want at all. Using self ensures that assignment sets the user’s remembertoken, and as a result it will be written to the database along with the other attributes when the user is saved.

Now we’re ready to write the first signin element, the sign_in function itself. As noted above, our desired authentication method is to place a (newly created) remember token as a cookie on the user’s browser, and then use the token to find the user record in the database as the user moves from page to page.

app/helpers/sessions_helper.rb

module SessionsHelper

  def sign_in(user)
    remember_token = User.new_remember_token
    cookies.permanent[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
    self.current_user = user
  end
end

Here we follow the desired steps: first, create a new token; second, place the unencrypted token in the browser cookies; third, save the encrypted token to the database; fourth, set the current user equal to the given user.

Each element in the cookie is itself a hash of two elements, a value and an optional expires date. For example, we could implement user signin by placing a cookie with value equal to the remember token that expires 20 years from now:

cookies[:remember_token] = { value:   remember_token,
                             expires: 20.years.from_now.utc }

This pattern of setting a cookie that expires 20 years in the future became so common that Rails added a special permanent method to implement it, so that we can simply write

cookies.permanent[:remembertoken] = remembertoken

Under the hood, using permanent causes Rails to set the expiration to 20.years.from_now automatically.

After the cookie is set, on subsequent page views we can retrieve the user with code like:

User.findby(remembertoken: remember_token)

Having discussed how to store the user’s remember token in a cookie for later use, we now need to learn how to retrieve the user on subsequent page views. Let’s look again at the sign_in function to see where we are:

module SessionsHelper

  def sign_in(user)
    remember_token = User.new_remember_token
    cookies.permanent[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
    self.current_user = user
  end
end

The only code not presently working is

self.current_user = user

This code will never actually be used in the present application due to the immediate redirect, but it would be dangerous for the sign_in method to rely on this.

To start writing the code for current_user, note that the line

self.current_user = user

is an assignment, which we must define. Ruby has a special syntax for defining such an assignment function, shown: app/helpers/sessions_helper.rb

module SessionsHelper

  def sign_in(user)
    .
    .
    .
  end

  def current_user=(user)
    @current_user = user
  end

  def current_user
    @current_user     # Useless! Don't use this line.
  end
end

This might look confusing—most languages don’t let you use the equals sign in a method definition—but it simply defines a method currentuser= expressly designed to handle assignment to currentuser. In other words, the code

self.current_user = ...

is automatically converted to

current_user=(...)

thereby invoking the currentuser= method. Its one argument is the right-hand side of the assignment, in this case the user to be signed in. The one-line method body just sets an instance variable @currentuser, effectively storing the user for later use.

If we did this, we would effectively replicate the functionality of attraccessor. The problem is that it utterly fails to solve our problem: with the code in above "SessionsHelper", the user’s signin status would be forgotten: as soon as the user went to another page—poof!—the session would end and the user would be automatically signed out. This is due to the stateless nature of HTTP interactions—when the user makes a second request, all the variables get set to their defaults, which for instance variables like @currentuser is nil. Hence, when a user accesses another page, even on the same application, Rails has set @current_user to nil, and the code above won’t do what you want it to do.

To avoid this problem, we can find the user corresponding to the remember token created by the code in SessionsHelper, as shown above. Note that, because the remember token in the database is encrypted, we first need to encrypt the token from the cookie before using it to find the user in the database. We accomplish this with the User.encrypt method defined below.
Finding the current user using the remembertoken.
app/helpers/sessions
helper.rb

module SessionsHelper
  .
  .
  .
  def current_user=(user)
    @current_user = user
  end

  def current_user
    remember_token = User.encrypt(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end
end

I hope you could learnt the basic of authenticity token / sessions in ruby on rails

Credits: https://www.railstutorial.org/ - Michael Hartl

2 Responses
Add your response

This is a great tutorial, but you should credit the original author Michael Hartl for his work on Ruby on Rails Tutorial.

over 1 year ago ·

Add a credit to Michael Hartl please -- <a href="https://www.railstutorial.org">Rails Tutorial by Michael Hartl</a>

over 1 year ago ·