Rails 7 adds the ability to check if a location is safe before redirecting


A lot of times when working on web applications, a need arises to redirect a user without the user explicitly clicking on a link. For example, assume that a user has just created a post on an application. Once the post is created, it makes sense to redirect the user to the new post location, instead of back to the creation page.

Before

There are a few ways to calculate where a user must be navigated to, but a popular way is to pass a redirect_url query parameter. However, this opens up a vulnerability for an intruder to override this parameter and send users to unsafe locations. Rails already rejects unsafe redirects, but it does not provide a fallback location in cases where we still want the redirect.

class SignInsController < ApplicationController
  def create
    if SignIn.authenticate(sign_in_params)
      redirect_to params[:return_to]
    else
      render :new, status: :unprocessable_entity
    end
  end
end

As per the above example, an intruder can pass any malicious URL in params[:return_to] and redirect the user to an unsafe location.

After

Rails 7 adds the ability to identify if URL is internal and safe to redirect. A URL is safe if it’s internal, i.e., the same as the requesting host. If a redirect URL is not an internal one, it will redirect to a fallback location.

We need to use url_from method that accepts the return url as below:

def return_url
  url_from(params[:return_to]) || root_path
end

And here’s how to use it,

class SignInsController < ApplicationController
  def create
    if SignIn.authenticate(sign_in_params)
      redirect_to return_url
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

    def return_url
      url_from(params[:return_to]) || root_url
    end
end

Note

url_from should not be confused with the url_for method. url_from accepts an external parameter and validates the passed URL. url_for generates an internal URL from various options from within the app e.g., url_for(@user).

Join Our Newsletter