I Like My Coffee Strong ... And My Params Stronger

Before understanding the need for strong params in Rails, let’s take a look at the idea of mass assignment in Ruby.

Mass assignment is a convenient technique rubyists can use to easily assign multiple attributes to an object at instantiation. Yanik Jayaram provides an excellent description of it in his blog post. It uses elements of metaprogramming and the idea of dynamic definition which is artfully described by Emily Xie here.

This pattern becomes extremely useful in Rails when dealing with user input in forms. A sign up form is a great place for this pattern.

Let’s take a look at an example:

Sign Up

And here is the markup used to create that form:

1
2
3
4
5
6
7
<form action="/create"   method="post">
  <input type="text"     name="user[first_name]"            placeholder="First name"      value="">
  <input type="text"     name="user[last_name]"             placeholder="Last name"       value="">
  <input type="email"    name="user[email]"                 placeholder="Email Address"   value="">
  <input type="password" name="user[password]"              placeholder="Password"                >
  <input type="password" name="user[password_confirmation]" placeholder="Confirm Password"        >
</form>

Once the user submits this form, the params hash will inlude all the input values neatly nested as key value pairs within the sub key user hash. Then, it would be appropriate to assume somewhere in the UsersController, there is a method like this:

1
2
3
4
5
6
7
8
9
10
11
UsersController

  def create
    @user = User.new(params[:user])
      if @user.save
        redirect_to @user
      else
        "Error!"
      end
  end
end

Instead of having to individually call the following methods, first_name=, last_name=, email=, and password= to set all of these input values … mass assignment lets us get it done at a higher level of abstraction.

But with this convenience lies a crucial vulnerability. In the form example above, we can assume that this web application is backed by a database with a users table that may have more columns than the form leads us to believe. Perhaps administrators also have user accounts within the application and certain permissions are allocated to them. This could be based on an admin column that takes a boolean value of 0 or 1.

Using this knowledge, we can maliciously change any one of the inputs to alter the admin column instead … even if this wasn’t the original intent of the form. Using Google’s Chrome browser, I can inspect the element of the form and change the email input field to reference user[admin] instead of user[email]

That might look something like this:

Sign Up

1
2
3
4
5
6
7
<form action="/create"   method="post">
  <input type="text"     name="user[first_name]"            placeholder="First name"      value="">
  <input type="text"     name="user[last_name]"             placeholder="Last name"       value="">
  <input type="email"    name="user[admin]"                 placeholder="Email Address"   value="">
  <input type="password" name="user[password]"              placeholder="Password"                >
  <input type="password" name="user[password_confirmation]" placeholder="Confirm Password"        >
</form>

Now when I submit this form, the controller is going to create a record in the users table that is missing my email address. Instead, it will set the admin column to true and give me admin access rights to the application. Depending on the domain, I might be able to see and do a multitude of things I normally couldn’t otherwise. That seems not preferable.

Luckily part of the Rails 4.0 core is a pattern that reinforces the idea of strong params. If we were to generate a scaffold using the user resource detailed above, it would produce 2 methods in the controller that look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Rails 4.0

UsersController

  def create
    @user = User.new(user_params)
      if @user.save
        redirect_to @user
      else
        "Error!"
      end
  end

  private
    def user_params
      params.require(:user).permit(:first_name, :last_name, :email, :password)
    end
  end
end

This pattern illustrates the idea of sanitizing the parameters before letting them get anywhere near our domain model. Instead of directly taking in the params submitted through the internet form in the create method, we will first call the private method user_params (named after the resource). This says it should require a sub key of user (resource name) and then only permit certain keys to make it through to the domain model. In effect, this creates a white list of acceptable inputs or strong parameters.

Now, the worst thing that could happen is a form that is submitted with incomplete user data. I think most would agree that is better than the former scenario.

The Rails scaffold comments on this pattern directly in the controller file:

Never trust parameters from the scary internet, only allow the white list through.

I like to imagine DHH himself saying this to really drive this point home.

So you too should enjoy your parameters and coffee the same way: strong. Your code will be more defensive and the data will be cleaner in the long run.


Additional Resources:

Documentation
DHH’s Blog Post
Pivotal Labs on Testing Strong Params
Strong Params Gem for Earlier Ruby Versions

Comments