Ruby on Rails – ODIN Project – 4

odinForms and Authentication

This section gets into some of the areas of web apps that are less glamorous than they are important. Forms are your user’s window to interact with your application. Authentication is critical for many applications, and you’ll build a couple of auth systems from the ground up.


 Step 1: Form Basics

Half refresher, half expanding your mind, this will bridge the gap between the lowly web form and your server side logic.

Introduction

How much do you REALLY know about forms? It may sound strange, but forms are possibly the most complicated thing about learning web development. Not necessarily because the code itself is difficult, but because you usually want to build forms that accomplish so many different things at once.

Up until now, we’ve been thinking about Models in Rails on sort of a one-off basis. The User model. The Post model. Sometimes we’ve had the models relate to each other via associations, like that a Post can has_many Comment objects. Usually, though, we tend to silo our thoughts to only deal with one at a time.

Now think about a web form to buy an airline ticket. You probably need to enter your name, address, phone number, email, the airline, the flight number, the flight date, your credit card number, your credit card security number, your card expiration date, your card’s zipcode, and a bunch of checkboxes for additional things like trip insurance. That’s a whole lot of different models embedded in one form! But you still submit it with a single button.

Most forms won’t be that long or complicated for you, but it’s useful to appreciate all the things you can (and one day will) do with them. It’s incredibly easy to make a basic form so the first thing we’ll do is make sure you’ve got an intimate understanding of how forms are created in HTML and then how Rails offers you some helpers to make your life easier. We’ll cover the way data is structured and sent to the controller until you feel pretty comfortable with that. Then a later lesson will deal with how to take that basic understanding and make forms handle some more firepower.

Points to Ponder

Look through these now and then use them to test yourself after doing the tasks

  • How can you view what was submitted by a form?
  • What is a CSRF Token and why is it necessary?
  • How do you generate the token in Rails?
  • Why is the name attribute of a form input element so important?
  • How can you nest attributes under a single hash in params?
  • Why is this useful?
  • What do you have to add/modify in your controller to handle nested params?
  • What special tags does Rails’ #form_tag helper give you?
  • What is the difference between #form_tag and #form_for helpers?
  • How do you access errors on a failed-to-save model object?
  • Which form helper automatically adds markup around errors?
  • How do you access your Update or Delete actions with a form?

 Forms in HTML

Step one is to be able to create a form in HTML. Remember how that looks?

    <form action="/somepath" method="post">
      <input type="text">
      ...
      
      ...
      <input type="submit" value="Submit This Form">
    </form>

There are plenty of input tags to choose from, including button, checkbox, date, hidden, password, radio and many more.


Viewing What Your Form Submits

If you want to see what your forms are submitting to your Rails app, look through the output that gets printed into your console when you run your $ rails server. Whenever you submit a very basic form for a user email signup, it should include lines that look something like:

Started POST "/user" for 127.0.0.1 at 2013-11-21 19:10:47 -0800
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "email"=>"foo@bar.com", "commit"=>"Submit Form"}

Note: This is from a form that might be generated by a Rails helper method, as explained in a later section below

The first line tells us which HTTP method was used and which route the form went to. The second line tells us which controller and action the form will be handled by. The third line contains everything that will get stuffed into the params hash for the controller to use. We’ll talk about the contents in the next sections.

You’ll find yourself looking at this server output a lot when you start building forms. It’ll keep you sane because it tells you exactly what the browser sent back to your application so you can see if there’s been a… misunderstanding.


 Railsifying Your Form

The first thing you’ll realize if you try to create a plain vanilla form in a Rails view is that it won’t work. You’ll either get an error or your user session will get zeroed out (depending on your Rails version). That’s because of a security issue with cross-site scripting, so Rails requires you to verify that the form was actually submitted from a page you generated. In order to do so, it generates an authenticity token which looks like gibberish but helps Rails match the form with your session and the application.

You’ll notice the token in the server output from above:

...
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "email"=>"foo@bar.com", "commit"=>"Submit Form"}
...

So, if you want to create your own form that gets handled by Rails, you need to provide the token somehow as well. Luckily, Rails gives you a method called form_authenticity_token to do so, and we’ll cover it in the project.

    <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">

 Making Forms into params

What about the other form inputs, the ones we actually care about?

Each one of these inputs is structured slightly differently, but there are some commonalities. One important thing to note is the name attribute that you can give to an input tag. In Rails, that’s very important. The name attribute tells Rails what it should call the stuff you entered in that input field when it creates the params hash. For instance,

    ...
    <input type="text" name="description">
    ...

Will result in your params hash containing a key called description that you can access as normal, ie. params[:description], inside your controller. That’s also why some inputs like radio buttons (where type="radio") use the name attribute to know which radio buttons should be grouped together such that clicking one of them will unclick the others. The name attribute is surprisingly important!

Now another thing we talked about in the controller section was nesting data. You’ll often want to tuck submitted data neatly into a hash instead of keeping them all at the top level. This can be useful because, as we saw with controllers, it lets you do a one-line #create (once you’ve whitelisted the parameters with #require and #permit). When you access params[:user], it’s actually a hash containing all the user’s attributes, for instance {first_name: "foo", last_name: "bar", email: "foo@bar.com"}. How do you get your forms to submit parameters like this? It’s easy!

It all comes back to the name attribute of your form inputs. Just use hard brackets to nest data like so:

    ...
    <input type="text" name="user[first_name]">
    <input type="text" name="user[last_name]">
    <input type="text" name="user[email]">
    ...

Those inputs will now get transformed into a nested hash under the :user key. The server output becomes:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "user"=>{"first_name"=>"foo","last_name"=>"bar","email"=>"foo@bar.com"}, "commit"=>"Submit Form"}

Specific parameters of the params hash are accessed like any other nested hash params[:user][:email].

Don’t forget that you have to whitelist the params now in your controller using require and permit because they are a hash instead of just a flat string. See the Controller section below for a refresher on the controller side of things.

This is cool stuff that you’ll get a chance to play with in the project.


 Form Helpers

Rails tries to make your life as easy as it can, so naturally it provides you with helper methods that automate some of the repetitive parts of creating forms. That doesn’t mean you don’t need to know how to create forms the “old fashioned” way. It’s actually MORE important to know your form fundamentals when using helpers because you’ll need to really understand what’s going on behind the scenes if something breaks.

Start by making a form using the form_tag helper, which takes a block representing all the inputs to the form. It takes care of the CSRF security token we talked about above by automatically creating the hidden input for it so you don’t have to. You pass it arguments to tell it which path to submit to (the default is the current page) and which method to use. Then there are tag helpers that create the specified tags for you, like text_field_tag below. All you need to specify there is what you want to call the field when it is submitted.

    <%= form_tag("/search", method: "get") do %>
      <%= label_tag(:q, "Search for:") %>
      <%= text_field_tag(:q) %>
      <%= submit_tag("Search") %>
    <% end %>

Creates the form:

    <form accept-charset="UTF-8" action="/search" method="get">
      <label for="q">Search for:</label>
      <input id="q" name="q" type="text" />
      <input name="commit" type="submit" value="Search" />
    </form>

The ID of the inputs matches the name.

There are tag helpers for all the major tags and the options they accept are all a bit different. See the reading assignment for more detail.


 Handy Shortcuts: form_for

No one wants to remember to specify which URL the form should submit to or write out a whole bunch of *_tag methods, so Rails gives you a shortcut in the form of the slightly more abstracted form_for method. It’s a whole lot like form_tag but does a bit more work for you.

Just pass form_for a model object, and it will make the form submit to the URL for that object, e.g. @user will submit to the correct URL for creating a User. Remember from the lesson on controllers that the #new action usually involves creating a new (unsaved) instance of your object and passing it to the view. Now you finally get to see why by using that object in your #form_for forms!

Where form_tag accepted a block without any arguments and the individual inputs had to be specified with something_tag syntax, form_for actually passes the block a form object and then you create the form fields based off that object. It’s conventional to call the argument simply f.

From the Rails Guide:

    # app/controllers/articles_controller.rb
    def new
      @article = Article.new
    end

    #app/views/articles/new.html.erb
    <%= form_for @article do |f| %>
      <%= f.text_field :title %>
      <%= f.text_area :body, size: "60x12" %>
      <%= f.submit "Create" %>
    <% end %>

And the generated HTML is:

    <form accept-charset="UTF-8" action="/articles/create" method="post">
      <input id="article_title" name="article[title]" type="text" />
      <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea>
      <input name="commit" type="submit" value="Create" />
    </form>

Note that this helper nests the Article’s attributes (the hard brackets in the name attribute should be the dead giveaway).

The best part about form_for is that if you just pass it a model object like @article in the example above, Rails will check for you if the object has been saved yet. If it’s a new object, it will send the form to your #create action. If the object has been saved before, so we know that we’re editing an existing object, it will send the object to your #update action instead. This is done by automatically generating the correct URL when the form is created.


 Forms and Validations

What happens if your form is submitted but fails the validations you’ve placed on it? For instance, what if the user’s password is too short? Well, first of all, you should have had some Javascript validations to be your first line of defense and they should have caught that… but we’ll get into that in another course. In any case, hopefully your controller is set up to re-render the current form.

You’ll probably want to display the errors so the user knows what went wrong. Recall that when Rails tries to validate an object and fails, it attaches a new set of fields to the object called errors. You can see those errors by accessing your_object_name.errors. Those errors have a couple of handy helpers you can use to display them nicely in the browser — #count and #full_messages. See the code below:

    <% if @post.errors.any? %>
      <div id="error_explanation">
        <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>

        <ul>
        <% @post.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
        </ul>
      </div>
    <% end %>

That will give the user a message telling him/her how many errors there are and then a message for each error.

The best part about Rails form helpers… they handle errors automatically too! If a form is rendered for a specific model object, like using form_for @article from the example above, Rails will check for errors and, if it finds any, it will automatically wrap a special <div> element around that field with the class field_with_errors so you can write whatever CSS you want to make it stand out.


 Making PATCH and DELETE Submissions

Forms aren’t really designed to natively delete objects because browsers only support GET and POST requests. Rails gives you a way around that by sticking a hidden field named “method” into your form. It tells Rails that you actually want to do either a PATCH (aka PUT) or DELETE request (whichever you specified), and might look like `<input name=”method” type=”hidden” value=”patch”>`.

You get Rails to add this to your form by passing an option to form_for or form_tag called :method, e.g.:

    form_tag(search_path, method: "patch")

 Controller-Side Refresher

Just as a refresher, here’s a very basic controller setup for handling #new actions and #create actions.

    # app/controllers/users_controller.rb
    ...
    def new
      @user = User.new
    end

    def create
      @user = User.new(user_params)
      if @user.save
        redirect_to @user
      else
        render :new
      end
    end

    def user_params
      params.require(:user).permit(:first_name, :last_name, :other_stuff)
    end
    ...

I wanted to show this again so you could remind yourself what’s going on in the form’s lifecycle. The user presumably went to the path for the #new action, likely http://www.yourapp.com/users/new. That ran the #new action, which created a new user object in memory (but not yet saved to the database) and rendered the new.html.erb view. The view probably used form_for on @user to make things nice and easy for the developer.

Once the form gets submitted, the #create action will build another new User object with the parameters we explicitly tell it are okay. Recall that our custom #user_params method will return the params[:user] hash for us, which lets User.new build us a complete new instance. If that instance can be saved to the database, we’re all good and we go to that user’s show.html.erb page.

If the @user cannot be saved, like because the first_name contains numbers, we will jump straight back to rendering the new.html.erb view, this time using the @user instance that will still have errors attached to it. Our form should gracefully handle those errors by telling the user where he/she screwed up.


 Your Tasks

1. Read the Rails Guides Form Helpers (http://guides.rubyonrails.org/form_helpers.html). Sections 1 to 3.2. Skim 3.3 to 7 to see what kinds of things are out there. One day you’ll need them, and now you know where to look. Read sections 7.1 and 7.2 for the official explanation of how parameters are created from the name attribute.

2. Read the Rails Guides Active Record Validations (http://guides.rubyonrails.org/active_record_validations.html#displaying-validation-errors-in-views) especially section 8 for a quick look at displaying errors


 Conclusion

At this point, you should have a solid understanding of how forms work in general and a pretty good feel for how Rails helps you out by generating them for you. You’ll get a chance to build forms in the next few projects.


 Additional Resources


Project: Forms

These projects will give you a chance to actually build some forms, both using nearly-pure HTML and then graduating to using the helper methods that Rails provides. The tutorial chapter will cover integrating a signup form with your Rails application and providing help if the user enters incorrect information.

 Project 1: Bare Metal Forms and Helpers

In this project, you’ll build a form the old fashioned way and then the Rails way.


 Your Task

Set up the Back End

You’ll get good at setting up apps quickly in the coming lessons by using more or less this same series of steps:

1. Build a new rails app (called “re-former”).

2. Go into the folder and create a new git repo there. Check in and commit the initial stuff.

3. Modify your README file to say something you’ll remember later.

4. Create and migrate a User model with :username, :email and :password.

5. Add validations for presence to each field in the model.

6. Create the :users resource in your routes file so requests actually have somewhere to go. Use the :only option to specify just the :new and :create actions.

7. Build a new UsersController (either manually or via the $ rails generate controller Users generator).

8. Write empty methods for #new and #create in your UsersController.

9. Create your #new view in app/views/users/new.html.erb.

10. Fire up a rails server in another tab.

11. Make sure everything works by visiting http://localhost:3000/users/new in the browser.


HTML Form

The first form you build will be mostly HTML. Build it in your New view at app/views/users/new.html.erb. The goal is to build a form that is almost identical to what you’d get by using a Rails helper so you can see how it’s done behind the scenes.

1. Build a form for creating a new user. Specify the method and the action attributes in your <form> tag (use $ rake routes to see which HTTP method and path are being expected based on the resource you created). Include the attribute accept-charset="UTF-8" as well, which Rails naturally adds to its forms to specify Unicode character encoding.

2. Create the proper input tags for your user’s fields (email, username and password). Use the proper password input for “password”. Be sure to specify the name attribute for these inputs. Make label tags which correspond to each field.

3. Submit your form and view the server output. Oops, we don’t have the right CSRF authenticity token (ActionController::InvalidAuthenticityToken) to protect against cross site scripting attacks and form hijacking.

4. Include your own authenticity token by adding a special hidden input and using the #form_authenticity_token method. This method actually checks the session token that Rails has stored for that user (behind the scenes) and puts it into the form so it’s able to verify that it’s actually you submitting the form. It might look like:

    # app/views/users/new.html.erb
    ...
    <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
    ...

5. Submit the form again. Great! Success! We got a Template is missing error instead and that’s A-OK because it means that we’ve successfully gotten through our blank #create action in the controller (and didn’t specify what should happen next, which is why Rails is looking for a app/views/users/create.html.erb view by default). Look at the server output above the error’s stack trace. It should include the parameters that were submitted, looking something like:

    Started POST "/users" for 127.0.0.1 at 2013-12-12 13:04:19 -0800
    Processing by UsersController#create as HTML
    Parameters: {"authenticity_token"=>"WUaJBOpLhFo3Mt2vlEmPQ93zMv53sDk6WFzZ2YJJQ0M=", "username"=>"foobar", "email"=>"foo@bar.com", "password"=>"[FILTERED]"}

That looks a whole lot like what you normally see when Rails does it.

6. Go into your UsersController and build out the #create action to take those parameters and create a new User from them. If you successfully save the user, you should redirect back to the New User form (which will be blank) and if you don’t, it should render the :new form again (but it will still have the existing information entered in it). You should be able to use something like:

    # app/controllers/users_controller.rb
    def create
      @user = User.new(username: params[:username], email: params[:email], password: params[:password])
      if @user.save
        redirect_to new_user_path
      else
        render :new
      end
    end

7. Test this out. Can you now create users with your form? If so, you should see an INSERT SQL command in the server log.

8. We’re not done just yet… that looks too long and difficult to build a user with all those params calls. It’d be a whole lot easier if we could just use a hash of the user’s attributes so we could just say something like User.new(user_params). Let’s build it… we need our form to submit a hash of attributes that will be used to create a user, just like we would with Rails’ form_for method. Remember, that method submits a top level user field which actually points to a hash of values. This is simple to achieve, though – just change the name attribute slightly. Nest your three User fields inside the variable attribute using brackets in their names, ie. name="user[email]".

9. Resubmit. Now your user parameters should be nested under the "user" key like:

    Parameters: {"authenticity_token"=>"WUaJBOpLhFo3Mt2vlEmPQ93zMv53sDk6WFzZ2YJJQ0M=", "user" => {"username"=>"foobar", "email"=>"foo@bar.com", "password"=>"[FILTERED]"}}

10.You'll get some errors because now your controller will need to change. But recall that we're no longer allowed to just directly call params[:user] because that would return a hash and Rails' security features prevent us from doing that without first validating it.

11.Go into your controller and comment out the line in your #create action where you instantiated a ::new User (we'll use it later).

12.Implement a private method at the bottom called user_params which will permit and require the proper fields.

13.Add a new ::new User line which makes use of that new whitelisting params method.

14. Submit your form now. It should work marvelously (once you debug your typos)!


Railsy Forms with #form_tag

Now we’ll start morphing our form into a full Rails form using the #form_tag and #*_tag helpers. There’s actually very little additional help that’s going on and you’ll find that you’re mostly just renaming HTML tags into Rails tags.

1. Comment out your entire HTML form. It may be helpful to save it for later on if you get stuck.

2. Convert your <form> tag to use a #form_tag helper and all of your inputs into the proper helper tags via #*_tag methods. The good thing is that you no longer need the authentication token because Rails will insert that for you automatically.

3. See the Ruby on Rails ActionView::Helpers::FormTagHelper Documentation (http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag) for a list and usage of all the input methods you can use with #form_tag.

4. Test out your form. You’ll need to change your #create method in the controller to once again accept normal top level User attributes, so uncomment the old User.new line and comment out the newer one.

You’ve just finished the first step.


 

Railsy-er Forms with #form_for

#form_tag probably didn’t feel that useful — it’s about the same amount of work as using <form>, though it does take care of the authenticity token stuff for you. Now we’ll convert that into #form_for, which will make use of our model objects to build the form.

1. Modify your #new action in the controller to instantiate a blank User object and store it in an instance variable called @user.

2. Comment out your #form_tag form in the app/views/users/new.html.erb view (so now you should have TWO commented out form examples).

3. Rebuild the form using #form_for and the @user from your controller.

4. Play with the #input method options. Add a default placeholder (like “example@example.com” for the email field), make it generate a different label than the default one (like “Your user name here”), and try starting with a value already populated.

5. Test it out. You’ll need to switch your controller’s #create method again to accept the nested :user hash from params.


 Editing

1. Update your routes and controller to handle editing an existing user. You’ll need your controller to find a user based on the submitted params ID.

2. Create the Edit view at app/views/users/edit.html.erb and copy/paste your form from the New view. Your HTML and #form_tag forms (which should still be commented out) will not work — they will submit the form as a POST request when you need it to be a PATCH (PUT) request (remember your $ rake routes?). It’s an easy fix, which you should be able to see if you attempt to edit a user with the #form_for form (which is smart enough to know if you’re trying to edit a user or creating a new one).

3. Do a “view source” on the form generated by #form_for in your Edit view, paying particular attention to the hidden fields at the top nested inside the <div>. See it?

4. Try submitting an edit that you know will fail your validations. #form_for also automatically wraps your form in some formatting (ie. a red border on the input field) if it detects errors with a given field.

5. Save this project to Git and upload to Github.


 Extra Credit

  • Modify your form view to display a list of the error messages that are attached to your failed model object if you fail validations. Recall the #errors and #full_messages methods. Start by displaying them at the top and then modify….

 Project 2: Ruby on Rails Tutorial

This chapter will take what you now know about forms and make it part of a real application instead of just a learning exercise. You’ll build out the user signup form for the Twitter-clone project and integrate it with the validations you created on the database in the previous chapter.


Your Task

 1. Complete the Ruby on Rails Tutorial Chapter 7: Sign Up (https://www.railstutorial.org/book/sign_up)


Step 2: Sessions, Cookies, and Authentication

Learn how to store data in the user’s browser and how that is used to sign in the user and keep them signed in across requests.


 Introduction

“Sessions” are the idea that your user’s state is somehow preserved when he/she clicks from one page to the next. Remember, HTTP is stateless, so it’s up to either the browser or your application to “remember” what needs to be remembered.

In this lesson you’ll learn about sessions, browser cookies, and how authentication is built in Rails. We’ll cover both home-grown authentication and the most commonly used authentication gem, Devise.

Points to Ponder

Look through these now and then use them to test yourself after doing the tasks

  • What is a session?
  • How is the session “hash” different from the cookies “hash”?
  • What is the flash “hash” used for?
  • When would you need to use flash.now instead of flash?
  • What are controller filters and why are they useful?
  • How do you run a controller filter for just a specific few actions?
  • What’s the difference between authentication and authorization?
  • Why is #has_secure_password a handy method?
  • What is the basic overview of how to authenticate a user with that method?
  • What additional steps (on a high level) are needed to actually “remember” a user after they’ve closed the browser?
  • What is the Devise gem and why is it useful?

 Cookies, Sessions, and Flashes

Cookies, Sessions and Flashes are three special objects that Rails 4 gives you which each behave a lot like hashes. They are used to persist data between requests, whether until just the next request, until the browser is closed, or until a specified expiration has been reached. In addition to different temporal concerns, they each solve slightly different use cases, covered below.


 Cookies

Cookies are key-value data pairs that are stored in the user’s browser until they reach their specified expiration date. They can be used for pretty much anything, most commonly to “bookmark” the user’s place in a web page if she gets disconnected or to store simple site display preferences. You could also store shopping cart information or even passwords but that would be a bad idea. You shouldn’t store anything in regular browser cookies that needs to either be secure or persisted across browser sessions. It’s too easy for users to clear their cache and/or steal/manipulate unsecured cookies.

To work with cookies, Rails gives you access to a special hash called cookies, where each key-value pair is stored as a separate cookie on the user’s browser. If you were to save cookies[:hair-color] = "blonde", you’d be able to pull up your browser’s developer tools and see a cookie on the user’s browser that has a key of hair-color and a value of blonde. Delete it using cookies.delete(:hair-color).

With each new request to your server, the browser will send along all the cookies and you can access them in your controllers and views like a normal hash. You can also set their expiration dates, for example using syntax like cookies[:name] = { value: "cookies YUM", expires: Time.now + 3600}.


 Sessions

Think about how websites keep track of how a user is logged in when the page reloads. HTTP requests are stateless so how can you tell that a given request actually came from that particular user who is logged in? This is why cookies are important. They allow you to keep track of your user from one request to another until the cookie expires.

A special case is when you want to keep track of data in the user’s “session”, which represents all the stuff your user does while you’ve chosen to “remember” her, typically until the browser window is closed. In that case, every page she visits until the browser is closed will be part of the same session.

To identify a user’s session information, Rails stores a special secure and tamper-proof cookie on the user’s browser that contains their entire session hash (look for it in your developer tools, usually under the “Resources” section) and it expires when the browser is closed. Whenever the user makes a request to your application, that request will also automatically include that session cookie (along with the other cookies) and you can use it to keep track of her logged-in state. This may all seem abstract now, but you’ll get a chance to see it in action shortly.

Rails gives you access to the session hash in an almost identical way to the above-mentioned cookies hash. Use the session variable in your views or controllers like so:

    # app/controllers/users_controller.rb
    ...
    # Set a session value
    session[:current_user_id] = user.id

    # Access a session value
    some_other_variable_value = session[:other_variable_key]

    # Reset a session key
    session[:key_to_be_reset] = nil

    # Reset the entire session
    reset_session
    ...

Why would you need both cookies and sessions? They are similar but not the same. session is an entire hash that gets put in the secure session cookie that expires when the user closes the browser. If you look in your developer tools, the “expiration” of that cookie is “session”. Each value in the cookies hash gets stored as an individual cookie.

So cookies and sessions are sort of like temporary free database tables for you to use that are unique to a given user and will last until you either manually delete them, they have reached their expiration date, or the session is ended (depending on what you specified).


A Few Additional Notes on Sessions and Cookies

  • session and cookies aren’t really hashes, Rails just pretends they are so it’s easy for you to work with them. You can still consider them as hashes just because they act very similarly to hashes.
  • You are size-limited in terms of how much you can store inside a session hash or browser cookie (~4kb). It is sufficient for any “normal” usage, but don’t go pretending either of these are actually substitutes for a database.

 Flashes

You’ve already seen and used the flash hash by now, but we’ll cover it again from the perspective of understanding sessions. flash is a special hash (okay, a method that acts like a hash) that persists only from one request to the next. You can think of it as a session hash that self destructs after it’s opened. It’s commonly used to send messages from the controller to the view so the user can see success and failure messages after submitting forms.

If you want to pop up “Thanks for signing up!” on the user’s browser after running the #create action (which usually uses redirect_to to send the user to a totally new page when successful), how do you send that success message? You can’t use an instance variable because the redirect caused the browser to issue a brand new HTTP request and so all instance variables were lost.

The flash is there to save the day! Just store flash[:success] (or whatever you’d like it called) and it will be available to your view on the next new request. As soon as the view accesses the hash, Rails erases the data so you don’t have it show up every time the user hits a new page. So clean, so convenient.

What about cases where the user can’t sign up because of failed validations? In this case, the typical #create action would just render the #new action using the existing instance variables. Since it’s not a totally new request, you’ll want to have your error message available immediately. That’s why there’s the handy flash.now hash, e.g. flash.now[:error] = "Fix your submission!". Just like the regular flash, this one self destructs automatically after opening.

You still have to write view code to display the flash messages. It’s common to write a short view helper that will pin any available flash message(s) to the top of the browser. You might also add a class to the message which will allow you to write some custom CSS, for instance turning :success messages green and :error messages red.

    # app/views/layouts/application.html.erb
    ...
    <% flash.each do |name, message| %>
      <div class="<%= name %>"><%= message %></div>
    <% end %>

 Controller Filters

Before we talk about authentication, we need to cover controller filters. The idea of these filters is to run some code in your controller at very specific times, for instance before any other code has been run. That’s important because, if a user is requesting to run an action they haven’t been authorized for, you need to nip that request in the bud and send back the appropriate error/redirect before they’re able to do anything else. You’re basically “filtering out” unauthorized requests.

We do this through the use of a “before filter”, which takes the name of the method we want to run:

    # app/controllers/users_controller
    before_action :require_login
    ...
    private
    def require_login
      # do stuff to check if user is logged in
    end

The before_action method takes the symbol of the method to run before anything else gets run in the controller. If it returns false or nil, the request will not succeed.

You can specify to only apply the filter for specific actions by specifying the only option, e.g. before_action :require_login, only: [:edit, :update]. The opposite applies by using the :except option… it will run for all actions except those specified.

You’ll want to hide your filter methods behind the private designation so they can only be used by that controller.

Finally, filters are inherited so if you’d like a filter to apply to absolutely every controller action, put it in your app/controllers/application_controller.rb file.


 Authentication

The whole point of authentication is to make sure that whoever is requesting to run an action is actually allowed to do so. The standard way of managing this is through logging in your user via a sign in form. Once the user is logged in, you keep track of that user using the session until the user logs out.

A related concept is authorization. Yes, you may be signed in, but are you actually authorized to access what you’re trying to access? The typical example is the difference between a regular user and an admin user. They both authenticate with the system but only the admin is authorized to make changes to certain things.

Authentication and authorization go hand in hand. You first authenticate someone so you know who they are and can check if they’re authorized to view a page or perform an action. When you build your app, you’ll have a system of authentication to get the user signed in and to verify the user is who he says he is. You authorize the user to do certain things (like delete stuff) based on which methods are protected by controller filters that require signin or elevated permissions (ie. admin status).


 Basic and Digest Authentication

If you’re looking for a very casual and insecure way of authenticating people, HTTP Basic authentication can be used. We won’t cover the details here, but it basically involves submitting a username and password to a simple form and sending it (unencrypted) across the network. You use the #http_basic_authenticate_with method to do so (see the reading for examples) and to restrict access to certain controllers without it.

For a slightly more secure (over HTTP) authentication system, use HTTP Digest Authentication. We’ll again not cover it here. It relies on a #before_action running a method which calls upon #authenticate_or_request_with_http_digest, which takes a block that should return the “correct” password that should have been provided.

The problem with both of these is that they hard code user names and passwords in your controller (or somewhere), so it’s really just a band-aid solution.


Rolling Your Own Auth

If you want user logins, you’ll need to go through a few extra steps. We won’t cover them explicitly here because you’ll get a chance to do them in the project. A few principles are useful to know going in, though. The following stuff may seem a bit abrupt and devoid of examples, but it’s really just a preview of what you’ll be doing shortly in the project.

First, we don’t store passwords in plain text in the database. That’s just asking for trouble (how many news stories have you seen about major sites getting hacked and passwords being exposed in plain text?). Instead, you’ll store an encrypted “password digest” version of the password.

When a user submits their password via the login form, instead of comparing it directly with a plaintext version of that password, you actually convert the submitted password into digest form. You’ll then compare that new digest with the digest you’d previously stored from the user. If they match, you’ve got yourself a logged in user.

This is much better because digests are one-way encryption. You can easily create a digest from a password string, but it’s extremely difficult to decrypt the digest and retrieve the original password. The most effective way to crack a bunch of digests is just to make a giant list of possible passwords, turn them into digests, and see if those digests match the one you’re trying to crack (i.e. guess-and-check on a massive scale).

Rails doesn’t make you do everything yourself. It has a method called #has_secure_password which you just drop into your User model and it will add a lot of the functionality you’re looking for. To work with that handy method, you basically set up your User model to handle accepting password and password_confirmation attributes but you won’t actually persist those to the database. has_secure_password intercepts those values and converts them into the password digest for you.

To initialize a new user session (when your user signs in), you’ll need to create a new controller (usually sessions_controller.rb) and the corresponding routes for :new, :create and :destroy. If the user passes the correct credentials (which we can check using the #authenticate method), you’ll use the session variable to store their ID, which you can use to validate that they are who they say they are. This is a simple way of authenticating the user that uses Rails’ existing session infrastructure, but only lasts as long as the session does.

If your user wants to be “remembered” (you’ve probably seen the “remember me” checkbox plenty of times on login forms), you need a way to remember them for longer than just the length of the browser session. To do this, you’ll need to create another column in your Users table for an encrypted remember_token (or whatever you’d like to call it). You’ll use that to store a random string for that user that will be used in the future to identify him/her.

You will drop the unencrypted token as a permanent cookie (using cookies.permanent[:remember_token]) into the user’s browser. That cookie will be submitted with each new request, so you can check with the encrypted version in the database to see who that user is whenever they make a request. This is basically a more explicit and permanent version of what Rails is doing with sessions. It’s best practice to reset the token on each new signin if the user signs out.

It’s usually good to make some helper methods for yourself to cover common behavior like signing in a user, checking if a user is signed in, and comparing the currently signed in user with another user (useful if the current user is looking at another user’s page and shouldn’t see links to “edit” it). These things will make your life much easier since you can reuse them in your controller filters or your views or even your tests.

A generic step-by-step overview:

1. Add a column to your Users table to contain the user’s password_digest.

2. When the user signs up, turn the password they submitted into digest form and then store THAT in the new database column by adding the has_secure_password method to your User model.

3. Don’t forget any necessary validations for password and password confirmation length.

4. Build a sessions controller (and corresponding routes) and use the #authenticate method to sign in the user when the user has submitted the proper credentials using the signin form.

5. Allow the user to be remembered by creating a remember_token column in the Users table and saving that token as a permanent cookie in the user’s browser. Reset on each new signin.

6. On each page load that requires authentication (and using a #before_action in the appropriate controller(s)), first check the user’s cookie remember_token against the database to see if he’s already signed in. If not, redirect to the signin page.

7. Make helper methods as necessary to let you do things like easily determine if a user is signed in or compare another user to the currently signed in user.


 Devise

Devise is a gem which has been built to handle all this stuff for you. It may be tempting to immediately dive into it, but that’s not a good idea for beginners. It’s first of all quite important to understand the basic steps of authentication. Devise can also get fairly complex if you start running into problems or nonstandard use cases. It’s more useful for intermediate+ users of Rails than beginners.

That said, you’ll end up using it on most of your projects once you’ve mastered rolling your own authentication. It’s ultimately better than rolling your own auth because they’ve covered a lot of the edge cases and security loopholes that you might not think about. Devise lets you interface with more advanced authentication systems for talking to APIs like OAuth. So it’s quite useful down the road.

In a short word, Devise prepackages for you a bunch of signin and signup forms and methods to help implement them. It’s made up of 10 modules (and you can choose which ones you want to use). You install the devise gem and run the installer to drop their files into your application. You’ll also need to run a database migration to add their additional fields to your Users table.

Configuration will be dependent on your use case. Do you want to make the user confirm their signup using an email (the Confirmable module)? Do you want to allow the user to reset his password (the Recoverable module)?

It’s beyond the scope of this lesson to teach Devise but you’ll certainly be using it by the end of the course. The trick is to read the documentation.  The point of showing it here is for you to see it, read it, and keep it in the back of your head until you actually use it.


 Your Task

  1. Read Rails Guides Action Controller Overview – Chapters 5 and 6 (http://guides.rubyonrails.org/action_controller_overview.html).  Don’t worry too much about the details of session_store configurations in 5.1 right now.

2. Read Rails Guides Action Controller Overview – Chapter 8 (http://guides.rubyonrails.org/action_controller_overview.html) to understand controller filters.

3. Read Rails Guides Action Controller Overview – Chapter 11 (http://guides.rubyonrails.org/action_controller_overview.html) to understand more about authentication.

4. Look at the Devise documentation (https://github.com/plataformatec/devise).  Read about how to install it in your Rails App and what the different modules do. You don’t need to use Devise just yet, so this is more of a reconnaissance mission for later.


 Conclusion

Authentication can appear to be a fairly complicated topic.  There are a lot of moving parts. At it’s core, though, you’re just checking whether the person making a request is actually a signed in user who has the permissions to do so, all by using browser cookies in some form or another.

This lesson should have given you some appreciation for how complicated login systems can potentially get but it should also have removed the veil from the websites you’ve visited countless times. Auth isn’t rocket science and you’ll shortly be building it into your own applications.


 Additional Resources


Project: Authentication

You’ll build a closed community for sharing embarrassing gossip with the world.

In these projects, you’ll be working to implement authentication systems so users can only access areas of a site they are authorized to.


Project 1: Ruby on Rails Tutorial

We’re starting to get into the meaty stuff with the tutorial. Take your time and pay attention to which file (especially for the specs) you’re supposed to be writing in. A common and frustrating mistake is to put your describe block in the wrong place.

You’ll implement signin and signout functionality for the user, which opens the door to allow more destructive user behaviors like editing and deleting things (which should only be allowed for a given user or admin). In Chapter 9, you’ll get the chance to implement these functions.


 Your Task

  1. Complete the Ruby on Rails Tutorial – Chapter 8: Log in, log out (https://www.railstutorial.org/book/log_in_log_out)
  2. Complete the Ruby on Rails Tutorial – Chapter 9: Updating, showing and deleting users (https://www.railstutorial.org/book/updating_and_deleting_users)

 Project 2: Members Only!

In this project, you’ll be building an exclusive clubhouse where your members can write embarrassing posts about non-members. Inside the clubhouse, members can see who the author of a post is but, outside, they can only see the story and wonder who wrote it.

This will be a chance for you to “roll your own” authentication system, very similar to how you did in the tutorial. As usual, we will be focusing on data and function, not style. If you want to add your own stylistic flourishes, consider it extra credit.

It’s easy to feel a bit overwhelmed by building your own authentication. That’s because there are several moving parts. The session controller/form, hanging onto and refreshing the remember token when necessary, and using that token to check up on the current user. It may help if you write out the steps as you understand them prior to getting started, so you know what you’re shaky on and will need to pay attention to.


 Your Task

The projects will be less and less explicit about how to achieve their goals, since we expect you to build on your previous knowledge. If you don’t know how to do something, feel free to check back in previous lessons or projects or Google the correct way to implement it.  The Ruby on Rails Tutorial will be a good reference.

Up until now, we’ve been treating the “password” field of user submissions pretty cavalierly… it’s mostly been an afterthought included for the sake of completeness. We certainly haven’t been storing them properly or encrypting them, which we should. So wipe all that stuff from your memory because now you’ve got the tools to properly encrypt user passwords and we’ll be doing it from now on. Actually, you’ve had the tools since back when you did chapter 6 of the Ruby on Rails Tutorial.

If you’d like to challenge yourself, don’t even follow the steps below, just go ahead and build the app!


 Basic Setup

1.Think about and spec out how to set up your data models for this application. You’ll need users with the usual simple identification attributes like name and email and password but also some sort of indicator of their member status. They’ll need to create posts as well. Given what you know about passwords, you’ll be using a :password_digest field instead of a :password field.

2. Create your new members-only Rails app and Github repo. Update your README.

3. Start by migrating and setting up your basic User model (no membership attributes yet).

4. Include the bcrypt-ruby gem in your Gemfile. $ bundle install it. (note: This might just be bcrypt)

5. Add the #has_secure_password method to your User file.

6. Go into your Rails console and create a sample user to make sure it works properly. It probably looks something like: User.create(:name => "foobar", :email => "foo@bar.com", :password => "foobar", :password_confirmation => "foobar")

7. Test the #authenticate command which is now available on your User model (thanks to #has_secure_password) on the command line — does it return the user if you give it the correct password?

    > user = User.create(:name => "foobar", :email => "foo@bar.com", :password => "foobar", :password_confirmation => "foobar")
    > user.authenticate("somethingelse")
    => false
    > user.authenticate("foobar")
    => true

 Sessions and Sign In

Now let’s make sure our users can sign in.

1. Create a sessions_controller.rb and the corresponding routes. Make “sign in” links in the layout as necessary.

2. Fill in the #new action to create a blank session and send it to the view.

3. Build a simple form with #form_for to sign in the user at app/views/sessions/new.html.erb. Verify that you can see the form.

4. We want to remember that our user is signed in, so you’ll need to create a new string column for your User table called something like :remember_token which will store that user’s special token.

5. When you create a new user, you’ll want to give that user a brand new token. Use a #before_create callback on your User model to:

  • Create a remember token (use SecureRandom.urlsafe_base64 to generate a random string)
  • Encrypt that token (with the Digest::SHA1.hexdigest method on the stringified (#to_s) version of your token)
  • Save it for your user.

6. Create a couple of users to populate your app with. We won’t be building a sign up form, so you’ll need to create new users via the command line. Your #before_create should now properly give each newly created user a special token.

7. Now fill in the #create action of your SessionsController to actually create the user’s session. The first step is to find the user based on their email address and then compare the hash of the password they submitted in the params to the hashed password stored in the database (using #authenticate).

8. Once you’ve verified that the user has submitted the proper password, sign that user in.

9. Create a new method in your ApplicationController which performs this sign in for you. Give the user a new remember token (so they don’t get stolen or stale). Store the remember token in the user’s browser using a cookie so whenever they visit a new page, we can check whether they are signed in or not. Use the cookies.permanent “hash” to do this.

10. Create two other helpful methods in your ApplicationController — one to retrieve your current user (#current_user) and another to set it (#current_user=(user)). Retrieving your current user should use the ||= operator — if the current user is already set, you should return that user, otherwise you’ll need to pull out the remember token from the cookie and search for it in your database to pull up the corresponding user. If you can’t find a current_user, return nil.

11. Set your current user whenever a user signs in.

12. Build sign out functionality in your SessionsController#delete action which removes the current user and deletes the remember token from the cookie. It’s best if you make a call to a method (e.g. #sign_out) in your ApplicationController instead of just writing all the functionality inside the SessionsController.

13. Create a link for signing out which calls the #delete method of your session controller. You’ll need to spoof the DELETE HTTP method, but that’s easily done by passing #link_to the option :method => :delete.


 Authentication and Posts

We’ll need to make sure only signed in users can see the author of each post. We’re not going to worry about editing or deleting posts.

1. Create a Post model and a Posts controller and a corresponding resource in your Routes file which allows the [:new, :create, :index] methods.

2. Atop your Posts Controller, use a #before_filter to restrict access to the #new and #create methods to only users who are signed in. Create the necessary helper methods in your ApplicationController.

3. For your Posts Controller, prepare your #new action.

4. Write a very simple form in the app/views/posts/new.html.erb view which will create a new Post.

5. Make your corresponding #create action build a post where the foreign key for the author (e.g. user_id) is automatically populated based on whichever user is signed in. Redirect to the Index view if successful.

6. Fill out the #index action of the PostsController and create the corresponding view. The view should show a list of every post.

7. Now add logic in your Index view to display the author’s name, but only if a user is signed in.

8. Sign in and create a few secret posts.

9. Test it out. Sign out and go to the index page. You should see a list of the posts but no author names. Sign in and the author names should appear. Your secrets are safe!

This is obviously a somewhat incomplete solution. We currently need to create new users from the Rails console. But it should give you some practice figuring out authentication. Feel free to improve it. Maybe you’ll be the next SnapChat.