Sometimes you’ll want to perform a redirect in a Rails controller to enforce canonical URLs. This comes into play when dealing with slugs. Take this URL, for example:
By Rails convention,
27 is the primary key of
Project model, and
mvp-launch is a slug (generated by customizing to_param or via a gem like friendly_id). The
ActiveRecord.find implementation will discard the non-integer part when performing a database query.
A consequence of this magic is that the slug portion of the ID doesn’t really matter, since it is discarded. If the name of the project changed and URLs no longer match, or if the end-user mistyped the URL, Rails will still dutifully load it.
To solve this, your controller can check for the accuracy of the slug and redirect if it doesn’t match the expected value.
The trouble with such a redirect is that any extra params in the original request are lost. Depending on the app, that might be undesirable. For example, consider a
search route within a project that has a URL like this:
redirect_to to apply a canonical project ID strips out the search params:
The obvious solution to retaining the search params is to reuse the
params object, but this is not allowed.
Performing a redirect by constructing a URL based on user input is inherently risky, and is a well-documented security vulnerability. This is essentially what you are doing when you call
redirect_to params.merge(...), because
params can contain arbitrary data the user has appended to the URL.
Starting with Rails 7, new Rails apps are configured to automatically prevent a certain type of redirect attack. However, if you have updated your project from an earlier Rails version, this setting might not be enabled.
To be safe, pass
allow_other_host: false to
redirect_to whenever you are redirecting to a URL that may have been built using user-provided data, as shown in the examples below.
ActionController::UnfilteredParameters error means that Rails wants you to use strong parameters, which is the safest solution.
If you are okay with the user appending arbitrary query params without enforcing an allow-list, you can bypass the strong params requirement by using
redirect_to( request.params.merge(project_id: canonical_slug), allow_other_host: false, status: :moved_permanently )