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.
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/sessionshelper.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
Written by Mert AKSOY
Related protips
2 Responses
This is a great tutorial, but you should credit the original author Michael Hartl for his work on Ruby on Rails Tutorial.
Add a credit to Michael Hartl please -- <a href="https://www.railstutorial.org">Rails Tutorial by Michael Hartl</a>