Handling CanCan::AccessDenied
In the Controller helpers chapter, we saw that when a resource is not authorized, a CanCan::AccessDenied exception is raised, and we offered a basic handling through config/application.rb. Let's now see what else we can do.
The CanCan::AccessDenied exception is raised when calling authorize! in the controller and the user is not able to perform the given action.
A message can optionally be provided.
authorize! :read, Article, :message => "Unable to read this article."This exception can also be raised manually if you want more custom behavior.
raise CanCan::AccessDenied.new("Not authorized!", :read, Article)The message can also be customized through internationalization.
# in config/locales/en.yml
en:
unauthorized:
manage:
all: "Not authorized to %{action} %{subject}."
user: "Not allowed to manage other user accounts."
update:
project: "Not allowed to update this project."
action_name:
model_name: "..."Notice manage and all can be used to generalize the subject and actions. Also %{action} and %{subject} can be used as interpolated variables in the message.
You can catch the exception and modify its behavior in the ApplicationController. The behavior may vary depending on the request format. For example here we set the error message to a flash and redirect to the home page for HTML requests and return 403 Forbidden for JSON requests.
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { head :forbidden }
format.html { redirect_to root_path, alert: exception.message }
end
end
endThe action and subject can be retrieved through the exception to customize the behavior further.
exception.action # => :read
exception.subject # => ArticleThe default error message can also be customized through the exception. This will be used if no message was provided.
exception.default_message = "Default error message"
exception.message # => "Default error message"Rescuing exceptions for XML responses
If your web application provides a web service which returns XML or JSON responses then you will likely want to handle Authorization properly with a 403 response. You can do so by rendering a response when rescuing from the exception.
rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { render nothing: true, status: :forbidden }
format.xml { render xml: '...', status: :forbidden }
format.html { redirect_to main_app.root_url, alert: exception.message }
end
endDanger of exposing sensible information
Please read this thread for more information.
In a Rails application, if a record is not found during load_and_authorize_resource it raises ActiveRecord::NotFound before it checks authentication in the authorize step.
This means that secured routes can have their resources discovered without even being signed in:
$ curl -I https://app.example.com/restricted_resource/does-not-exist
HTTP/1.1 404 Not Found
$ curl -I https://app.example.com/restricted_resource/does-exist-but-not-permitted
HTTP/1.1 302 Found
Location: https://app.example.com/sessions/newA more secure approach is to always return a 404 status instead of 302:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { render nothing: true, status: :not_found }
format.html { redirect_to main_app.root_url, notice: exception.message, status: :not_found }
format.js { render nothing: true, status: :not_found }
end
end
end