In order to redirect users to a custom web page each time params
contain a redirect_uri
value I am evaluating to use a custom redirect_to
method this way (by @kiddorails):
class ApplicationController < ActionController::Base
def custom_redirect_to(url)
redirect_to (params[:redirect_uri].present? ? params[:redirect_uri] : url)
end
end
def create
@post = Post.new(params)
if @post.save
custom_redirect_to @post
else
render 'new'
end
end
However I would like to be aware of possible drawbacks and to receive support for adopting the above solution.
Allowing a redirect target to be set through a URL parameter without any validation is potentially dangerous for your users, because it makes fishing attempts easier.
An attacker can send links to your users such as
http://my-trusted-site.com/some/action/path?redirect_uri=malicious-site-that-looks-like-trusted-site.com
, and many users will only see the domain part and fail to realize where they end up after clicking that link.
The Open Web Application Security Project (OWASP) therefor considers this a vulnerability:
Unvalidated redirects and forwards are possible when a web application
accepts untrusted input that could cause the web application to
redirect the request to a URL contained within untrusted input. By
modifying untrusted URL input to a malicious site, an attacker may
successfully launch a phishing scam and steal user credentials.
Because the server name in the modified link is identical to the
original site, phishing attempts may have a more trustworthy
appearance. Unvalidated redirect and forward attacks can also be used
to maliciously craft a URL that would pass the application’s access
control check and then forward the attacker to privileged functions
that they would normally not be able to access.
It's important that you check the redirect_uri parameter carefully before executing the redirect.
But since proper validation is tricky and prone to errors, an even better idea is not to accept URI parameters in the first place, but to allow certain keywords instead that specify where the user will be redirected.
class ApplicationController < ActionController::Base
protected
def dynamic_redirect_to(default_route, options)
options.stringify_keys!
redirect_to options.fetch(params[:redirect_to], default_route)
end
end
You can now define any number of allowed keywords in advance which may be used as the ?redirect_to=
parameter:
def create
@post = Post.new(params)
if @post.save
dynamic_redirect_to post_path(@post), edit: edit_post_path(@post)
else
render 'new'
end
end
If ?redirect_to=edit
is set, the user is redirected back to the edit page. If the parameter is not set or contains an unspecified keyword, she is redirected to the default post_path(@post)
instead.
I agree with what janfoeh said above.
But to implement your requirement, I hacked around in Rails code of redirection to make it simpler.
Make a file config/initializers/redirecting.rb with:
require 'abstract_controller/base'
module ActionController
module Redirecting
extend ActiveSupport::Concern
include AbstractController::Logger
include ActionController::RackDelegation
include ActionController::UrlFor
def _compute_redirect_to_location(options)
if redirect_url = session.delete(:redirect_uri)
options = redirect_url
end
case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
# See http://tools.ietf.org/html/rfc3986#section-3.1
# The protocol relative scheme starts with a double slash "//".
when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
options
when String
request.protocol + request.host_with_port + options
when :back
request.headers["Referer"] or raise RedirectBackError
when Proc
_compute_redirect_to_location request, options.call
else
url_for(options)
end.delete("\0\r\n")
end
end
end
In your app/controllers/application_controller.rb :
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
before_action :set_redirect_uri
protect_from_forgery with: :exception
def set_redirect_uri
session[:redirect_uri] = params[:redirect_uri] if params[:redirect_uri].present?
end
end
And Voila! Now you can continue to use the original redirect_to
, and whenever redirect_uri
is supplied in the url, it will set that url in session and automatically override. :)
Note: I am clearing session[:redirect_uri]
only when redirect_to
is called. You can easily modify that behavior to reset this session depending upon the requirement.