• Home
  • About
  • Portfolio
  • Contact
CodeCurious
  • Home
  • About
  • Portfolio
  • Contact
Go Back

Understanding Strong Parameters In Rails

Learn how Strong Parameters work in Rails to protect your application from mass assignment vulnerabilities safely.

Jean Emmanuel Cadet
By Jean Emmanuel Cadet • Ruby on Rails Developer

Last updated : Jul 27, 2026 • 21 min read

Understanding Strong Parameters in Rails

Last updated : Jul 27, 2026 • 21 min read

Share with friends

Every Rails application talks to the outside world through forms, APIs, and JSON payloads, and every single one of those inputs comes from someone you cannot fully trust. It does not matter how friendly your users are or how solid your frontend validation looks. Anyone with a browser's developer tools or a simple curl command can send your server whatever data they want, shaped however they want. If your controllers blindly accept that data and hand it to your models, you have opened the door to some of the most damaging vulnerabilities in web development.

This is exactly the problem Strong Parameters was built to solve. It is one of Rails' most important built in security features, and if you have ever written params.require(:user).permit(:name, :email) in a controller, you have already used it. But knowing the syntax and truly understanding what it protects against are two different things.

In this guide, you will learn what Strong Parameters actually are, why Rails introduced them, and how mass assignment vulnerabilities work under the hood. You will build a complete example from form to database, walk through the difference between require and permit, handle nested attributes and arrays, and learn why permit! should make you nervous. By the end, you will have a solid mental model for keeping user input under control in every Rails controller you write.


What Are Strong Parameters?

Strong Parameters is a Rails security feature that requires developers to explicitly declare which attributes from an incoming request are allowed to be used for mass assignment. In plain terms, it forces you to say "these specific fields, and only these fields, can be used to create or update a record."

Rails did not always work this way. Before Strong Parameters existed, Rails relied on a model level mechanism called attr_accessible (and its counterpart attr_protected) to control which attributes could be mass assigned. You would declare a whitelist directly on the model, something like attr_accessible :name, :email, and Rails would filter out anything not on that list when you called Model.new(params[:model]).

The problem was that this approach lived in the wrong place. Attribute protection is really about how a specific controller action should handle a specific request, not a fixed property of the model itself. A User model might need different permitted attributes depending on whether an admin is updating a user versus the user updating their own profile. Keeping that logic on the model made it easy to forget a whitelist entirely, and forgetting it meant every attribute became mass assignable by default, a dangerous default.

Rails 4 moved this responsibility into the controller with Strong Parameters, and the philosophy flipped from blacklisting dangerous fields to whitelisting safe ones. Nothing is permitted unless you explicitly say so. This single change closed off a huge class of mass assignment vulnerabilities that had affected real production applications before the change was introduced.

Understanding this history matters because it explains why Strong Parameters feels strict. That strictness is intentional, and it is the whole point.


Understanding Mass Assignment

Mass assignment is the practice of setting multiple attributes on a model at once using a hash, instead of assigning them one at a time. Rails makes this convenient with methods like new, create, and update, which all accept a hash of attributes.

# Mass assignment in action
User.create(name: "Amara", email: "[email protected]", role: "member")

That convenience becomes a vulnerability the moment the hash comes directly from user input rather than from your own code. Imagine a controller written without any parameter filtering:

class UsersController < ApplicationController
def update
@user = User.find(params[:id])
@user.update(params[:user])
redirect_to @user
end
end

This looks harmless if your edit form only includes a name and email field. But HTML forms are not the only way to send data to your server. A malicious user does not need your form at all. They can send a raw request with any fields they like:

curl -X PATCH https://example.com/users/42 \
-d "user[name]=Amara" \
-d "user[email][email protected]" \
-d "user[role]=admin"

If your User model has a role column and nothing is filtering incoming parameters, that role field gets mass assigned right along with name and email. A regular user just promoted themselves to admin. This is called a privilege escalation attack, and it is one of the most common outcomes of an unprotected mass assignment vulnerability.

The danger is not limited to role or permission fields. Think about a published boolean on an article, a verified flag on an account, a balance field on a wallet model, or a user_id foreign key that should only ever be set by your own server logic based on the currently logged in user. Any attribute your model exposes for mass assignment is fair game to an attacker unless you explicitly close that door. This is exactly why Strong Parameters exists, and why skipping it is never a shortcut worth taking.


How Strong Parameters Work

To understand Strong Parameters, you need to understand four pieces working together: params, ActionController::Parameters, require, and permit.

params is the hash-like object Rails builds for every incoming request. It contains route parameters, query string values, and submitted form or JSON body data, all merged into one structure you can access inside your controller actions.

ActionController::Parameters is the actual class that params is an instance of. It behaves a lot like a Ruby Hash, but it is not one. This distinction matters because ActionController::Parameters tracks whether its contents have been explicitly permitted. Every value coming from a request is treated as "unpermitted" until you say otherwise. If you try to pass an unpermitted ActionController::Parameters object directly into Model.new or Model.update, Rails raises a ActiveModel::ForbiddenAttributesError and refuses to proceed. This safeguard exists specifically so you cannot accidentally bypass the filtering system.

require tells Rails that a specific key must be present in the params hash, and it returns the value at that key. If the key is missing, Rails raises a ActionController::ParameterMissing error, which by default results in a 400 Bad Request response. This is useful for catching malformed requests early, before you even get to attribute filtering.

permit is where the actual whitelisting happens. It takes a list of attribute names and returns a new ActionController::Parameters object containing only those attributes, explicitly marked as permitted. Anything not listed is silently dropped (though Rails will log an "Unpermitted parameter" warning in development, which you will see often once you know to look for it).

Here is the request lifecycle in practice. A user submits a form. Rails parses the request body into the params hash. Your controller action calls something like params.require(:user).permit(:name, :email). This pulls out the user key, filters it down to just name and email, and returns a permitted ActionController::Parameters object. That object is now safe to pass into User.new or @user.update, because Rails knows exactly which attributes were allowed through, and nothing else made it in.


Your First Strong Parameters Example

Let's build a complete, working example so you can see every piece connected end to end. We will create a simple Article resource.

First, the migration:

# db/migrate/20260101000000_create_articles.rb
class CreateArticles < ActiveRecord::Migration[8.0]
def change
create_table :articles do |t|
t.string :title, null: false
t.text :body
t.boolean :published, default: false
t.references :user, null: false, foreign_key: true

t.timestamps
end
end
end

Next, the model. Nothing special is needed here for Strong Parameters, since that filtering happens in the controller, not the model.

# app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
validates :title, presence: true
end

Now the form, using Rails 8's form helpers:

<%# app/views/articles/_form.html.erb %>
<%= form_with model: @article do |form| %>
<div>
<%= form.label :title %>
<%= form.text_field :title %>
</div>

<div>
<%= form.label :body %>
<%= form.text_area :body %>
</div>

<div>
<%= form.label :published %>
<%= form.check_box :published %>
</div>

<%= form.submit %>
<% end %>

Notice this form does not include a field for user_id. That association should never come from client input. It should always be set on the server based on the currently authenticated user.

Finally, the controller:

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :authenticate_user!

def create
@article = current_user.articles.build(article_params)

if @article.save
redirect_to @article, notice: "Article created successfully."
else
render :new, status: :unprocessable_entity
end
end

def update
@article = current_user.articles.find(params[:id])

if @article.update(article_params)
redirect_to @article, notice: "Article updated successfully."
else
render :edit, status: :unprocessable_entity
end
end

private

def article_params
params.require(:article).permit(:title, :body, :published)
end
end

Walk through what happens on submission. The form posts a nested article hash containing title, body, and published. In the controller, article_params calls params.require(:article), which pulls that nested hash out and raises an error if it is missing entirely. Then .permit(:title, :body, :published) filters it down to exactly those three keys.

Because we scope the query with current_user.articles.build and current_user.articles.find, there is no way for a user to create or edit an article that does not belong to them, and there is no user_id field for an attacker to tamper with in the first place. This combination, Strong Parameters plus scoping queries to the current user, is what real production security looks like. Neither one alone is enough.


require vs permit

It is worth being precise about what each method actually does, because conflating them is one of the most common sources of confusion for developers new to Rails.

require is about presence. It asserts that a key must exist in the params hash, and it extracts the value at that key. It has nothing to do with which attributes are safe to mass assign. If you call params.require(:article) and there is no article key in the request, you get a ParameterMissing exception immediately, before any filtering logic even runs.

permit is about mass assignment safety. It takes the hash returned by require (or a slice of params directly) and produces a filtered, explicitly whitelisted version.

A common mistake is treating require as if it were doing the security work:

# Wrong: this "requires" article but permits everything, which raises
# ForbiddenAttributesError when passed to update or create
def article_params
params.require(:article)
end

This will actually blow up at runtime, because the returned object is still unpermitted. That is Rails protecting you from yourself.

Another mistake is skipping require entirely and just calling permit on the top level params:

# Fragile: works only if the client happens to send fields at the top level
def article_params
params.permit(:title, :body, :published)
end

This might technically run without errors, but it silently ignores the nested structure your form actually sends (params[:article][:title], not params[:title]), so your attributes will always come through blank. Always pair require for the nested key with permit for the individual fields, unless you have a specific reason not to nest.


Permitting Multiple Attributes

Strong Parameters handles ordinary scalar types without any special syntax. Strings, numbers, booleans, and dates are all permitted the same simple way, by listing the attribute name:

def event_params
params.require(:event).permit(
:name, # string
:capacity, # integer
:is_public, # boolean
:starts_at # date or datetime
)
end

Rails does not care about the underlying database type when permitting a scalar attribute. Type coercion (turning the string "42" into the integer 42, for example) happens later, at the Active Record layer, not during permission filtering. Strong Parameters is only concerned with whether a key is allowed through, not what type of value it holds.

A common incorrect implementation is trying to nest scalar values as if they were hashes:

# Incorrect: capacity is a plain integer field, not a nested hash
params.require(:event).permit(capacity: [:min, :max])

If your form genuinely needs a range, that should be two separate fields (capacity_min and capacity_max), each permitted individually, unless you are intentionally modeling a nested attribute, which we will cover next.


Nested Strong Parameters

Real applications rarely deal with flat data. Forms often include associated records, and Strong Parameters has a specific syntax for handling that nesting. If you want a refresher on how those associations actually work at the model level, our guide on how Active Record associations work in Ruby on Rails is a good companion to this section.

For a nested hash, like an address embedded in a user form, you pass a hash inside the permit call:

def user_params
params.require(:user).permit(:name, :email, address_attributes: [:street, :city, :zip_code])
end

This works hand in hand with accepts_nested_attributes_for on the model:

# app/models/user.rb
class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
end

And a form field that generates the matching nested structure:

<%= form_with model: @user do |form| %>
<%= form.text_field :name %>
<%= form.text_field :email %>

<%= form.fields_for :address do |address_form| %>
<%= address_form.text_field :street %>
<%= address_form.text_field :city %>
<%= address_form.text_field :zip_code %>
<% end %>

<%= form.submit %>
<% end %>

fields_for automatically names these fields user[address_attributes][street] and so on, which lines up exactly with what address_attributes: [:street, :city, :zip_code] expects in the controller.

For a has many association, like a user with multiple phone numbers, the nested attributes become an array of hashes, and you permit them the same way:

def user_params
params.require(:user).permit(
:name,
:email,
phone_numbers_attributes: [:id, :number, :label, :_destroy]
)
end

Notice the inclusion of :id and :_destroy. The :id field lets Rails know whether it is updating an existing associated record or creating a new one. The :_destroy field, paired with allow_destroy: true in accepts_nested_attributes_for, lets the form mark a nested record for deletion. Forgetting either of these is a very common source of "why isn't this nested form working" bugs.


Working with Arrays

Some parameters are not nested hashes at all, but simple arrays of scalar values, like a list of tag names or a list of selected category IDs. Strong Parameters has dedicated syntax for this case:

params.permit(tags: [])
params.permit(category_ids: [])

The empty array [] here is a signal to Rails that this key holds an array of permitted scalar values, rather than a hash of nested attributes. This is different from tags: [:name], which would tell Rails to expect an array of hashes, each containing a name key.

A full example for a multi-select category field might look like:

def article_params
params.require(:article).permit(:title, :body, category_ids: [])
end

With a form field generating array-style parameters:

<%= form.collection_check_boxes :category_ids, Category.all, :id, :name %>

This generates multiple article[category_ids][] inputs, and Rails collects them into an array automatically. Without the category_ids: [] syntax specifically, permit would reject the entire array and it would come through empty, which is a frustrating bug to track down if you do not know this distinction exists.


permit! Explained

permit! looks like a convenient shortcut. Instead of listing every attribute individually, it marks an entire params hash, and every nested hash inside it, as permitted, no matter what keys it contains.

def article_params
params.require(:article).permit!
end

This defeats the entire purpose of Strong Parameters. Every attribute on the model becomes mass assignable, which puts you right back in pre-Rails 4 territory, minus the model level protection that at least used to exist. Any field you add to the model in the future, even one that should never be user editable, becomes exploitable the moment it exists, with zero additional code required from an attacker.

There are narrow situations where permit! shows up in real codebases, most often in internal admin tools operating on trusted, already-authenticated staff input. Even then, most experienced Rails developers avoid it, because "trusted for now" has a way of becoming "forgotten and exploited later" as a codebase grows.

The better alternative is almost always to just write out the explicit list, even if it feels repetitive:

def article_params
params.require(:article).permit(:title, :body, :published, :category_id)
end

If you find yourself reaching for permit! because a form has dozens of fields, that is usually a sign the form or the underlying model could benefit from being broken into smaller, more focused pieces, not a reason to disable Rails' security filtering.


Strong Parameters and Security

Strong Parameters is your primary defense against several real attack patterns, and it is worth naming them explicitly so you recognize them in the wild.

Privilege escalation happens when a user modifies a field that grants them elevated permissions, like role, admin, or is_staff. If your parameter method never permits these fields, no request, however it is crafted, can touch them.

Protecting admin-only fields means being deliberate about which controller handles which fields. A common pattern is having separate parameter methods for regular users and admins, used in separate controller actions or even separate controllers entirely:

class Admin::UsersController < ApplicationController
before_action :require_admin!

private

def user_params
params.require(:user).permit(:name, :email, :role, :active)
end
end

class UsersController < ApplicationController
private

def user_params
params.require(:user).permit(:name, :email)
end
end

The regular UsersController simply never has the ability to touch role or active, regardless of what a malicious request contains, because those keys are not in its permit list at all.

Avoiding accidental exposure is about discipline as your models grow. Every time you add a new sensitive column, like stripe_customer_id, password_digest, or api_token, double check that it is not accidentally swept up into an existing permit list, especially if that list was written broadly at some point.

Common attack scenarios worth knowing by name include mass assignment of foreign keys (setting user_id on a record that should belong to someone else), mass assignment of state fields (flipping published, verified, or approved flags directly), and parameter pollution through unexpected nested structures that a poorly scoped permit call accepts without realizing it.


Common Mistakes

A few mistakes show up again and again in real Rails codebases, even among experienced developers.

Forgetting require leads to a NoMethodError or unexpected nil values, because you are operating on the raw top-level params hash instead of the nested resource hash your form actually submitted.

Forgetting permit entirely and passing raw params[:article] into Article.new raises ForbiddenAttributesError, which is Rails saving you from yourself, but it is worth understanding why it happens rather than just patching around it.

Using permit! everywhere, often copy-pasted from a tutorial or an old project, is one of the most dangerous habits to pick up. It is discussed in detail above, but it bears repeating here as a "common mistake" because it is genuinely common.

Permitting too many attributes "just in case" defeats the purpose of a whitelist. If a field is not actually needed by a given form, do not permit it in that controller action, even if the model has it.

Duplicate parameter methods across controllers, each maintained separately, tend to drift out of sync over time. If UsersController and Admin::UsersController both need overlapping fields, consider extracting shared logic into a concern or a dedicated parameter object rather than maintaining two nearly identical lists by hand.

Copy-paste mistakes are extremely common when duplicating a parameter method for a new resource and forgetting to update the field names, resulting in a method that quietly permits the wrong attributes or silently permits nothing useful at all.


Best Practices

A few habits will keep your parameter handling clean and secure as your application grows.

Keep one private parameter method per resource, named consistently, like article_params or user_params, placed at the bottom of the controller under a private keyword. This keeps the permitted attribute list easy to find and easy to audit.

Keep parameter methods small and focused on exactly what a given action needs. It is fine, and often correct, to have different permit lists for create versus update if the allowed fields genuinely differ.

Never trust client-side validation, including HTML5 required attributes or JavaScript form checks. These are usability features, not security features, and Strong Parameters exists precisely because client-side controls can always be bypassed.

Validate your models as well as your parameters. Strong Parameters controls which attributes can be set, but model validations control whether the values themselves are acceptable. Both layers matter, and neither replaces the other.

Test your parameter filtering directly, which we will cover in detail shortly, so that a future refactor cannot accidentally loosen a permit list without anyone noticing.

If your controller actions are also doing heavier lifting, like loading a user's articles along with their comments, it is worth pairing secure parameter handling with efficient querying. Our article on includes, preload, and eager_load in Ruby on Rails walks through how to load associated records without triggering unnecessary database queries in the same actions where you are handling Strong Parameters.


Strong Parameters with Nested Forms

Let's tie everything together with a slightly larger example: a simple blog application with users, articles, categories, and comments.

# app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :comments, dependent: :destroy
accepts_nested_attributes_for :comments, allow_destroy: true
end
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :authenticate_user!

def update
@article = current_user.articles.find(params[:id])

if @article.update(article_params)
redirect_to @article, notice: "Article updated."
else
render :edit, status: :unprocessable_entity
end
end

private

def article_params
params.require(:article).permit(
:title,
:body,
:category_id,
comments_attributes: [:id, :body, :_destroy]
)
end
end

A few things are worth calling out here. category_id is permitted directly as a scalar, since a belongs_to :category association is set through a plain foreign key field, not a nested hash. comments_attributes handles the has many relationship, permitting each comment's id (for identifying existing records), body (the editable field), and _destroy (for removing a comment through the form).

Because we scope the initial lookup with current_user.articles.find, a user cannot even attempt to update an article they do not own, regardless of what parameters they send. This scoping, combined with a precise permit list, is what makes nested forms safe to expose to end users. If this same controller were also rendering a list of related categories or comments elsewhere, this would be a good place to double check that those queries are not triggering N+1 issues. Our article on fixing N+1 queries in Rails walks through diagnosing and resolving that exact problem.


Testing Strong Parameters

Testing your permit lists directly protects you from a very specific and very common regression: someone removes a field from a permit list during a refactor, or adds one that should not be there, and nothing catches it until it reaches production.

A request spec is the most realistic way to test this, since it exercises the full stack from request to database:

# spec/requests/articles_spec.rb
require "rails_helper"

RSpec.describe "Articles", type: :request do
let(:user) { create(:user) }

before { sign_in user }

describe "PATCH /articles/:id" do
let(:article) { create(:article, user: user, title: "Original title") }

it "updates permitted attributes" do
patch article_path(article), params: { article: { title: "Updated title" } }

expect(article.reload.title).to eq("Updated title")
end

it "ignores unpermitted attributes like user_id" do
other_user = create(:user)

patch article_path(article), params: {
article: { title: "Updated title", user_id: other_user.id }
}

expect(article.reload.user_id).to eq(user.id)
end
end
end

The second example is the important one. It does not just check that the update succeeds, it verifies that a field which should be off limits, user_id in this case, stays unchanged even when an attacker tries to include it in the request.

it "raises when article key is missing" do
expect {
patch article_path(article), params: { title: "No article key" }
}.not_to change { article.reload.title }
end

Writing even a handful of these tests around your most sensitive resources, anything involving roles, ownership, or financial data, pays for itself the first time a refactor almost introduces a real vulnerability.


Frequently Asked Questions

What are Strong Parameters? Strong Parameters is a Rails security feature requiring developers to explicitly whitelist which request attributes can be used for mass assignment when creating or updating Active Record models.

Why do I need require? require asserts that a specific key must be present in the params hash and extracts its value. It does not perform any security filtering on its own, it just guarantees the nested hash you expect is actually there before you try to permit fields inside it.

What does permit! do? permit! marks an entire params hash, including all nested data, as permitted without any whitelist, which allows every attribute through and defeats the purpose of Strong Parameters. It should be avoided in almost all situations.

Can Strong Parameters replace model validations? No. Strong Parameters controls which attributes are allowed to be set. Model validations control whether the values of those attributes are actually acceptable, like checking presence, format, or length. You need both.

How do nested parameters work? Nested parameters are permitted using a hash syntax inside permit, such as address_attributes: [:street, :city], and they pair with accepts_nested_attributes_for on the model and fields_for in the form.

Why am I getting "Unpermitted parameter" warnings? This warning appears in your development log when the request includes a key that was not listed in your permit call. It is usually harmless, but it is worth checking, since it can also indicate a typo in your permit list or a frontend field that no longer matches your backend.

Should every controller have its own parameter method? Generally yes. Keeping a dedicated, clearly named private method per resource, scoped to exactly what each action needs, makes your permitted attributes easy to audit and reduces the risk of over-permitting fields across unrelated actions.


Conclusion

Strong Parameters is not just Rails boilerplate you write out of habit. It is a deliberate, explicit whitelist standing between untrusted user input and your database, and it is one of the clearest examples of Rails choosing safe defaults over convenient ones. Understanding the difference between require and permit, handling nested attributes and arrays correctly, and avoiding shortcuts like permit! will keep your application resistant to mass assignment vulnerabilities and privilege escalation attacks.

But Strong Parameters is only one layer. Building genuinely secure Rails applications means combining it with model validations that check the values themselves, authorization checks that confirm a user is allowed to act on a given record, and authentication that confirms who is making the request in the first place. Treat each layer as necessary but not sufficient on its own, and you will build applications that hold up under real-world pressure, not just in the happy path of your own testing.

💌 Don’t miss out! Join my newsletter for web development tips, tutorials, and insights delivered straight to your inbox.

Thanks for reading & Happy coding! 🚀

Follow me on:

Code. Learn. Grow.

A friendly newsletter sharing dev tips, lessons, and wins from my journey.

    Enter valid email address

    Services Tailored to Your Needs


    coding

    Web & Mobile Development

    Custom websites and mobile apps built to be fast, modern, and user-friendly. From sleek landing pages to full-scale applications, I deliver solutions that engage your audience and grow your business.

    API development

    Seamlessly connect your systems with secure, scalable APIs. I design and integrate APIs that improve efficiency, reliability, and flexibility for your business processes.

    Database design and management

    Reliable database solutions tailored to your needs. I design, optimize, and maintain databases that ensure performance, security, and scalability for your applications.

    You might also like…

    How to Optimize Active Record Queries in Rails
    Ruby On Rails

    How To Optimize Active Record Queries In Rails

    By Jean Emmanuel Cadet
    Published on: Jul 24, 2026
    Ruby on Rails Security Best Practices (2026 Guide)
    Ruby On Rails

    Ruby On Rails Security Best Practices (2026 Guide)

    By Jean Emmanuel Cadet
    Published on: Jul 22, 2026
    Ruby on Rails vs Laravel in 2026: Full Comparison
    Ruby On Rails

    Ruby On Rails Vs Laravel In 2026: Full Comparison

    By Jean Emmanuel Cadet
    Published on: Jul 20, 2026
    CodeCurious

    Designed for those who view software as architecture and code as literature.

    Legal

    Terms & Conditions Privacy Policy Disclaimer

    CodeCurious © 2025 - 2026. All rights reserved. | Made with ♥ by @jecode93