Basic Devise and Mass Assignment – Am I Missing Something?

Article summary

This week I’ve started adding real authentication to our application. We’re running the latest and greatest Rails, and we’re using ActiveRecord, so Devise looks like an excellent choice for authentication.

Except I’m running into a problem getting the basics wired up. In fact, mass assignment rules are getting in my way. Am I missing something?

Context

Instead of getting into unnecessary details about my application, I’m going to use Daniel Kehoe’s handy rails3-devise-rspec-cucumber Rails application as a starting point.

With the application setup and running, view the Login page and click the “Sign in” button with an empty form. You’ll get an “Invalid email or password” error. Cool. But now open up app/models/user.rb and remove password and password_confirmation fields from the attr_accessible list. Now submit the empty for again. Uh oh! Now we get an application error about how password is not mass assignable.

My first reaction to this was “why the hell is Devise trying to mass assign the password, especially on login?!?” I did some digging and found that there’s an interaction going on between Devise’s Devise::SessionsController#create and Devise::SessionsController#new. The create action is rejecting the login, as it should, and then running the new action. The new action then creates a new User object from the parameters. Now the first time you hit the new action, there are no parameters, so all is well. But when new is run in the context of create‘s parameters, we get a mass assignment error since password is in the parameters.

The Workaround

Assuming I want to keep password off the attr_accessible list, the best I could think of was to create my own controller that derives from Devise::SessionsController and configure Devise to use it for my session routes. Then, based on an idea Patrick gave me, I created a before_filter for the new action that strips the user parameter. Here’s the code.


class SessionsController < Devise::SessionsController
  before_filter only: :new do
    params.delete(:user)
  end
end

Pretty small. The downside here is that the other form fields, like Email and Remember me don’t get re-rendered. That’s a little obnoxious, but not the end of the world. At a later date I can write some fancier code to preserve those values.

A Question

So now my question is: am I missing something? It seems like password is an excellent candidate for a field that should not be mass assignable1. But perhaps in this context it’s ok? Or am I misusing Devise’s built in controllers?

Any insight here is appreciated.

1 I know little about the details, but GitHub’s recent mass assignment exploit has my mind particularly attuned to mass assignment protection, especially since I happen to be implementing login this week.

Conversation
  • Johnny Hall says:

    It’s been a wee while since I looked at Devise in detail but are you running Rails 3.2? Maybe this didn’t show up pre-3.2 because invalid mass-assignment used to just display a warning. So, it never caused a problem. Now, mass-assignment is illegal, you are getting an error.

    DHH posted this gist which shows how you they are handling mass-assignment.

  • SC says:

    I agree with you, Devise should not be using mass assignment.

    If you use Devise with Mongoid, the generated model will not include the attr_accessible declaration because Mongoid does not have a whitelist_attributes-like functionality enabled by default the way that Rails’ ActiveRecord does. If you subsequently enable it, you will have problems with Devise unless you add attr_accessible to your model.

    Perhaps Devise is using mass assignment in order to allow developers to add arbitrary fields to their Devise models? If they were not using mass assignment, the custom fields would get lost. I have to imagine there’s a better way to support that though.

  • Comments are closed.