Form Helpers

CSRF

In this chapter we'll be talking about forms and form helpers. The first one we'll be introducing is form_tag, but before we talk about the form_tag helper, let's first talk about the various reasons to use this helper.

CSRF or Cross-Site Request Forgery is a type of attack on an application. CSRF utilizes malicious code hidden in one application to affect the data in another application. For instance, consider a link written by a hacker, and in that link some sort of call to our application API is hidden within. Malicious code like this can sometimes be found within the attributes of image or link tags.

Here's an example what was mentioned above:

<a href="http://example.com" name="example" onclick="www.our_app/posts/1/delete">...</a>

If such a link was clicked, that html event of onclick would fire off. The value in onclick would get added to our cookies. If we were still logged into our app when we clicked the above link, then that cookie would be sent to our application, and verified with our current session id. Clicking the link above could then delete a post on our application without the user even realizing it.

Preventing CSRF

So, how do we prevent such an attack on our application. Rails does this with the use of an authenticity_token. This token is a random string stored in our session. Every time a javascript or html based request is made, it is checked for an authenticity token. If the token sent with the request from a form does not match the one stored in our application's session, then an exception will be thrown.

We can add this authenticity token to our session with one simple line, added to our application controller.

protect_from_forgery with: :exception

Let's start by enabling CSRF protection. We'll add the line of ruby code listed above to our Application Controller.

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  # ...
end

To test out this CSRF protection, we'll use our form for creating a new post. If this works as expected then we shouldn't be able to make a new post without that authenticity token within our form. First we'll need to make a new RESTful view for creating a new post; we can move the code from our new_post view to this file. Don't forget to change the local variable post to the instance variable that we are passing in from our PostsController, @post.

<!-- app/views/posts/new.html.erb -->
<html>
  <body>
    <!-- ... -->

    <form method="post" action="/posts">

      <label for="title">Title</label>
      <input id="title" name="title" type="text" value="<%= @post.title %>"/>
      <br /> <br />

      <label for="body">Body</label>
      <textarea id="body" name="body"><%= @post.body %></textarea>
      <br /> <br />

      <label for="author">Author</label>
      <input id="author" name="author" type="text" value="<%= @post.author %>"/>
      <br /> <br />

      <input type="submit" value="Create Post" />

    </form>

    <!-- ... -->
  </body>
</html>

Let's see what happens when we try to create a post:

Started POST "/posts" for ::1 at 2016-04-13 15:13:37 -0700
Processing by PostsController#create as HTML
  Parameters: {"title"=>"Another Post", "body"=>"Test", "author"=>"Missy"}
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms)

This occurs because we've turned on checks for CSRF authenticity tokens, but we haven't included one in our form. Let's do that now:

<!-- app/views/posts/new.html.erb -->

<html>
  <body>
    <!-- ... -->

    <form method="post" action="/posts">

      <label for="title">Title</label>
      <input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>">
      <input id="title" name="title" type="text" value="<%= @post.title %>"/>
      <br /> <br />

      <label for="body">Body</label>
      <textarea id="body" name="body"><%= @post.body %></textarea>
      <br /> <br />

      <label for="author">Author</label>
      <input id="author" name="author" type="text" value="<%= @post.author %>"/>
      <br /> <br />

      <input type="submit" value="Create Post" />

    </form>

    <!-- ... -->
  </body>
</html>

We set a hidden input element with a name of authenticity_token and a value that is set by the form_authenticity_token method. This method looks at the token saved in our session and returns it for use here.
With the code above, our form works, and this part of our code is now protected from a CSRF attack.

form_tag

One problem with our method above is that we'll have to remember to include this hidden input tag in all of our forms. Rails provides a form helper that gives us a bit of a shortcut. The form helper, form_tag generates an HTML form for us. If CSRF protection is enabled, then that hidden input is automatically added to our form. form_tag helper also gives us a few options for customizing our form in an effective and intuitive way.

Here is the code we would need to make the form used above with a form_tag helper:

<html>
  <body>
    <!-- ... -->

    <%= form_tag url_for(action: 'create'), method: "post" do %>

      <%= label_tag 'Title' %>
      <%= text_field_tag 'title', @post.title %>
      <br /> <br />

      <%= label_tag 'Body' %>
      <%= text_area_tag 'body', @post.body %>
      <br /> <br />

      <%= label_tag 'Author' %>
      <%= text_field_tag 'author', @post.author %>
      <br /> <br />

      <%= submit_tag "Create Post" %>

    <% end %>

    <!-- ... -->
  </body>
</html>

Options for form_tag

There are a lot of changes here, so let's go through them one by one. First, notice that we start with the syntax:

<%= form_tag url_for(action: 'create'), method: "post" do %>

We have form_tag, this is a form helper method that creates a form tag for us and allows us to specify various options for our form. To make sure that our form is submitted to the correct action, we can specify a url and the HTTP method(verb) used to submit this form.

We're using a Rails method to build up the URL we want the form to submit to. This method is url_for. It gives us several options to create a URL. One option is to specify only the action, as we do above: by doing so any other missing values from our URL are filled in with values from the current request.
The current request for making a new post would have a URL of /posts. This lets Rails know which controller this URL will be for. From there, specifying the action in url_for lets us further refine the URL based on our routes.

In this case, url_for(action: 'create') #=> '/posts'. This URL is pretty short, we could write it manually. But you could imagine that using url_for would be necessary for submitting forms to resources nested deep within our application.

We've previously also used URL path helpers to generate URLs. We'll be using these for our forms going forward, along with url_for.

The next part of our form_tag utilizes the option method. This option shows us one other situation where using form_tag versus writing out a form manually can be quite helpful. HTML forms can only use the GET and POST HTTP verbs. For some actions, we don't want to use GET or POST. If we want to delete a post we would want to use the DELETE HTTP verb.

Recall, that to do this we used a Rails convention where we had to set a hidden input field in our form. Then, we made sure to have a method attribute for that input tag of "delete"; this allowed us to submit a form with the DELETE HTTP verb. The method option does just that for us, depending on what value we set for the method option. If we use method="delete" in the form_tag above, then a hidden input field with method attribute delete will be automatically added to our form.

form_tag Helpers

Let's continue examining what makes up our form_tag and its helpers. For each tag we want to create, we have a helper method to dynamically make it.

  • label_tag<label></label>
  • text_field_tag<input type='text'>
  • text_area_tag<textarea><textarea>
  • submit_tag<input type='submit'>

Here is a list of the arguments we've used with these helpers and what each argument is used for:

  • The first argument for text_field_tag and text_area_tag helpers sets the name and id attributes for the tag we are creating. The second argument sets the content/value for that tag.
  • label_tag takes one to three arguments. If only one argument is passed, then that value is used to set the for attribute and the content for that label. If a second argument is passed in, then that second argument is used to set the content of the label instead. A third argument, as a hash, may be passed in to set additional HTML attributes.
  • submit_tag takes one to two arguments. The first argument sets the value of the submit tag, the second is a list of options for configuring the submit tag.

All of the form helpers listed above as well as many other form helpers can also be passed a list of options. We don't need those options at the moment though, so if you're curious about that information, you can find more on it in the relevant documentation.

Using form_tag for the Rest of Our Forms

We've gotten a look at form_tag and its field helper methods as well, the tag methods. Let's put that knowledge to good use. In this section, we'll convert the rest of our views that use plain HTML forms to use Rails form_tag. Here are some things to keep in mind when making the conversion.

Make sure that views:

  • are moved to their conventional directory locations
  • are renamed to their conventional names
  • links and forms are fixed to fit the new routes
  • forms use a hidden input with name="_method" to specify the correct HTTP verb when necessary

Recall that so far we have adjusted the view templates for:

  • posts: index and new
  • comments: index
<!-- app/views/posts/index.html.erb -->

<%= form_tag post_path(post.id), method: "delete", style: "display: inline" do %>
  <%= submit_tag "Delete" %>
<% end %>
<!-- app/views/posts/show.html.erb -->

<%= form_tag post_comment_path(@post.id, comment.id), method: "delete" do %>
  <%= submit_tag "Delete Comment" %>
<% end %>

<%= form_tag post_comments_path(@post.id) do %>

  <%= label_tag 'Comment' %>
  <%= text_area_tag 'body', @comment.body %>
  <br /> <br />

  <%= label_tag 'Name' %>
  <%= text_field_tag 'author', @comment.author %>
  <br /> <br />

  <%= submit_tag "Add Comment" %>
<% end %>
<!-- app/views/posts/edit.html.erb -->

<%= form_tag url_for(action: 'update'), method: "patch" do %>

  <%= label_tag 'Title' %>
  <%= text_field_tag 'title', @post.title %>
  <br /> <br />

  <%= label_tag 'Body' %>
  <%= text_area_tag 'body', @post.body %>
  <br /> <br />

  <%= label_tag 'Author' %>
  <%= text_field_tag 'author', @post.author %>
  <br /> <br />

  <%= submit_tag 'Update Post' %>

<% end %>

Parameter Naming Conventions and Mass Assignment

Let's go over what parameters from our form look like when we receive them in the controller. For this step, we'll put a binding.pry in our create action for the PostsController.

def create
  binding.pry
  @post = Post.new('author' => params[:author],
                   'title' => params[:title],
                   'body' => params[:body])

  if @post.save
    redirect_to posts_path
  else
    render 'new'
  end
end

Next, we'll fill out our new post form and submit it. If we look at our params hash, we'll see that author, title, and body each show up as individual keys at the highest level of this hash, along with the controller and action keys.

[1] pry(#<PostsController>)> params
=> {"utf8"=>"✓",
 "authenticity_token"=>"wYCWFeQFIDOLk4tkFd+gHUhLHlw5od3UL+RKcjKrXInSNDPZ+SfzYQrO3KW6TU0zwGuh36q4db1rfJzu+k/Kuw==",
 "title"=>"A New Post",
 "body"=>"Hello World",
 "author"=>"Alice Cooper",
 "commit"=>"Create Post",
 "controller"=>"posts",
 "action"=>"create"}

This may be fine with our current application. But if our form was more complicated or if we were passing more information to our controller, then we would maybe want further organization of the data related to our new post. Rails provides a way to organize related parameters into a sub-hash. To do this we need to change the name attribute for the form data that we want grouped together.

Here is what our form currently looks like:

<!-- app/views/posts/new.html.erb -->

<html>
  <body>
    <!-- ... -->

    <%= form_tag url_for(action: 'create'), method: "post" do %>
      <%= label_tag 'Title' %>
      <%= text_field_tag 'title', @post.title %>

      <br /> <br />

      <%= label_tag 'Body' %>
      <%= text_area_tag 'body', @post.body %>

      <br /> <br />

      <%= label_tag 'Author' %>

      <%= text_field_tag 'author', @post.author %>

      <br /> <br />

      <%= submit_tag "Create Post" %>
    <% end %>

    <!-- ... -->
  </body>
</html>

Now, let's change the form so that parameters related to our post are grouped together in our params hash.

<!-- app/views/posts/new.html.erb -->

<html>
  <body>
    <!-- ... -->

    <%= form_tag url_for(action: 'create'), method: "post" do %>
      <%= label_tag 'Title' %>
      <%= text_field_tag 'post[title]', @post.title %>

      <br /> <br />

      <%= label_tag 'Body' %>
      <%= text_area_tag 'post[body]', @post.body %>

      <br /> <br />

      <%= label_tag 'Author' %>

      <%= text_field_tag 'post[author]', @post.author %>

      <br /> <br />

      <%= submit_tag "Create Post" %>
    <% end %>

    <!-- ... -->
  </body>
</html>

Our params hash now looks like:

[1] pry(#<PostsController>)> params
=> {"utf8"=>"✓",
 "authenticity_token"=>"PSdbhGz0Cb2ic0PO2GwmNs9+3rSxLp0wNAVR6OHvGdIuk/5Icdba7yMuFA93/ssYR15hNyI3NVlwnYd0KQuP4A==",
 "post"=>{"title"=>"A New Post", "body"=>"Hello World", "author"=>"Alice Cooper"},
 "commit"=>"Create Post",
 "controller"=>"posts",
 "action"=>"create"}

Notice that everything is a bit easier to read. Any fields related to our post are in a nested hash. While we're at it, lets also make this change to our form used to create a new comment. Make sure that the file for showing a post and creating a comment follow Rails conventions.

  • views/application/show_post.html.erb -> views/posts/show.html.erb
  • comment -> @comment
  • post -> @post
  • comments -> @post.commments
<!-- app/views/posts/show.html.erb -->

<html>
  <body>
    <!-- ... -->
    <!-- We're using a url helper to post data to the correct action. -->

    <%= form_tag post_comments_path(@post.id) do %>
      <%= label_tag 'Comment' %>
      <%= text_area_tag 'comment[body]', @comment.body %>
      <br /> <br />

      <%= label_tag 'Author' %>
      <%= text_field_tag 'comment[author]', @comment.author %>
      <br /> <br />

      <%= submit_tag 'Add Comment' %>
    <% end %>

    <!-- ... -->
  </body>
</html>

Here is what our params hash looks like when we apply this rails parameter convention for creating a new comment:

[1] pry(#<CommentsController>)> params
=> {"utf8"=>"✓",
 "authenticity_token"=>"cz2+kiA91zb8sEdxotc6U7JCPTQ40ITM2M26oRQ2TYlgiRtePR8EZH3tELANRdd9OmKCt6vJLKWcVWw93NLbuw==",
 "comment"=>{"body"=>"New Comment", "author"=>"Number 5"},
 "commit"=>"Add Comment",
 "controller"=>"comments",
 "action"=>"create",
 "post_id"=>"23"}

If you are following along, there is something that you should have noticed after these changes. Our create actions don't seem to work. If we take a closer look at our actions we may notice the issue:

# /app/controllers/posts_controller.rb

# ...

def create
  @post = Post.new('author' => params[:author],
                   'title' => params[:title],
                   'body' => params[:body])

  if @post.save
    redirect_to posts_path
  else
    render 'new'
  end
end

# ...
# /app/controllers/comments_controller.rb

# ...

def create
  @comment = @post.build_comment(
    'body' => params[:body], 'author' => params[:author]
  )

  if comment.save
    # redirect for success
    redirect_to posts_path(@post.id)
  else
    # render form again with errors for failure
    render 'posts/show'
  end
end

Recall, that by changing the name attributes in our forms, we changed the structure of our parameters hash. So, if we want to access values related to a post, we use params[:post], and if we want to access values related to a comment, we'll have to use params[:comment].

Using the examples above we have:

params[:post] #=> {"title"=>"A New Post", "body"=>"Hello World", "author"=>"Alice Cooper"}
params[:comment] #=> {"body"=>"New Comment", "author"=>"Number 5"}

This structure for our parameters gives us a shorter, more concise syntax for object creation. Let's fix those create actions by using the correct calls to params.

# /app/controllers/posts_controller.rb

# ...

def create
  @post = Post.new(params[:post])

  if @post.save
    redirect_to posts_path
  else
    render 'new'
  end
end

# ...
# /app/controllers/comments_controller.rb

# ...

def create
  @comment = @post.build_comment(params[:comment])

  if comment.save
    # redirect for success
    redirect_to posts_path(@post.id)
  else
    # render form again with errors for failure
    render 'posts/show'
  end
end

Make sure to also apply these changes to the forms we've been using in our app. Also, make sure you update their corresponding controller actions as well.

There is a name for when we use a group of values to update an object. It's called mass assignment. By accessing a single k-v pair from our params hash, we're able to assign many attributes at once. While convenient, mass assignment also opens up our application to a security risk. How do we know that only the parameters we want assigned will be updated for our object?
By using mass assignment, we're blindly allowing any number of parameters for assignment, as long as they are included in our form. Rails does give us a way to safeguard our data when using mass assignment, which we'll learn about later on in this book.