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?
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_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
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.
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.
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.
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.
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.