I have a slightly customized Devise integration with a Rails 5 app. I'm inheriting from Devise::RegistrationsController and implementing my own create action for a User resource, and not calling super.
All of the time in development, and most of the time in production, params from the form come through like this:
{
"utf8"=>"✓",
"authenticity_token"=>"***",
"user"=> {
"email"=>"[email protected]",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"
},
"commit"=>"Create account"
}
But a minority of the time, in production only, params looks like this:
{
"utf8"=>"✓",
"authenticity_token"=>"***",
"user[email]"=>"[email protected]",
"user[password]"=>"[FILTERED]",
"user[password_confirmation]"=>"[FILTERED]",
"commit"=>"Create account",
"registration"=> {
"user[email]"=>"[email protected]",
"user[password]"=>"[FILTERED]",
"user[password_confirmation]"=>"[FILTERED]",
"commit"=>"Create account"
}
}
I don't know why this second form of the parameters appears. My assumption is that under some circumstances Devise processes the parameters differently. The only case I could think of to examine was re-submitting after a validation problem, and every time I try this the params still come through in the first form (in development and production).
The create action was written with the assumption that params[:user] has a hash of user attributes, which isn't the case for this second form. I can handle the two forms of these parameters, but I would like to understand why this happens and I do not want this action to be called with randomly different params.
In case it's relevant, the submission form (with styling classes omitted):
<%= form_for(@user, as: :user, url: registration_path(:user)) do |f| %>
<div class="row">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true %>
</div>
<div class="row">
<%= f.label :password %>
<%= f.password_field :password, autocomplete: "off" %>
</div>
<div class="row">
<%= f.label :password_confirmation, 'Password confirmation' %>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<div class="row">
<div>
<%= f.submit "Create account" %>
</div>
</div>
<% end %>
Edited with an update. Additional logging has confirmed something interesting: the request.media_type for the bugged requests is application/json (where for typical requests it should be application/x-www-form-urlencoded
Edited with update. It looks to me like the form data is being submitted with the wrong request type, this might explain both why values aren't being unpacked (why we have user[email] as an attribute name in the params) and why we're getting the params wrapped in that registration object (I have wrap_parameters enabled for JSON).
What this doesn't explain is why only some requests are getting tagged with this media type.
Final Edit: after observing my logs for a time I conclude that this issue is being caused by bots. The failures always occur as part of 3 requests from the same IP, with only the last having JSON data, all within a few seconds. The bot is just reading the form and converting it to JSON in a way that doesn't make sense for Rails. Thank you to the folks who took a look at this.
This happens when the
ActionController::ParamsWrapperkicks in.When enabled, the params wrapper wraps incoming parameters with the current controller's name when not already nested under that name.
In your example, you are sending parameters nested under
userto a controller namedRegistrationsController. Therefore, the ParamsWrapper wraps them withregistrationagain. If you had sent them to aUsersControllerthey would not be changes, because they are already nested underuser.IMHO, this is one of Ruby on Rails' most useless and error-prone magic behavior. And I usually disable it right away, because it often leads to issues. For example, when the frontend doesn't do any nesting and just depends on this feature, but the backend needs to restructure and introduce namespaces or rename controllers.