Strong parameters stop mass assignment by refusing to pass request parameters into a model until you list the allowed keys. Use permit for the list, expect to require and permit in one call, and avoid permit!, which allows every current and future attribute.
The problem it solves
Mass assignment means building a model from a hash of request data. If the model has an admin column and the hash contains admin: true, the attacker has just promoted themselves. The Rails controller guide says parameters cannot be used in mass assignment until they are explicitly permitted. Its example, Person.create(params[:person]), raises ActiveModel::ForbiddenAttributesError because nothing was permitted.
permit
permit lists the keys you allow, and unlisted keys are filtered out.
params.permit(:id, :name)
Values must be permitted scalars. Arrays are written as tags: [] and arbitrary hashes as options: {}. Be deliberate with that last one, since it accepts whatever keys arrive.
expect
The guide recommends expect for requiring and permitting together:
params.expect(user: [:username, :password])
If the root key is missing or the shape is wrong, it raises an error and the request gets a 400 Bad Request. That is better than a nil error deeper in your code.
Nested attributes
Both methods handle nesting. The guide shows double brackets to explicitly require an array of hashes, and gives books_attributes: [[ :title, :id, :_destroy ]] as an example for accepts_nested_attributes_for. Include :id and :_destroy only when you mean to let users update or delete child records.
Why permit! is the dangerous one
permit! permits an entire hash without checking its values. The guide warns that extreme care should be taken, because it allows all current and future model attributes to be mass assigned. The word future matters. A column added next quarter becomes assignable by anyone, with no code change in the controller.
Habits
- One private method per controller that returns the permitted parameters.
- Never include role, admin, owner or tenant columns in a user facing permit list.
- Set ownership from the signed in user, not from a hidden field.
- Search the codebase for
permit!and justify each hit.
Quick check
Is filtering out unpermitted keys enough, or should I raise?
Filtering keeps the app running but hides attack attempts. Many teams raise in development and log in production so mistakes are visible.
Does strong parameters replace authorization?
No. It controls which fields can be set, not who may set them on which record.
Sources
Last reviewed: 19 October 2026.