Does Rails come with a "not authorized" exception?
Solution 1
Rails doesn't seem to map an exception to :unauthorized
.
The default mappings are defined in activerecord/lib/active_record/railtie.rb:
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
'ActiveRecord::RecordNotSaved' => :unprocessable_entity
)
and actionpack/lib/action_dispatch/middleware/exception_wrapper.rb:
@@rescue_responses.merge!(
'ActionController::RoutingError' => :not_found,
'AbstractController::ActionNotFound' => :not_found,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::UnknownHttpMethod' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::UnknownFormat' => :not_acceptable,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
'ActionDispatch::ParamsParser::ParseError' => :bad_request,
'ActionController::BadRequest' => :bad_request,
'ActionController::ParameterMissing' => :bad_request
)
You could add a custom exception from within your application's configuration (or a custom Railtie):
Your::Application.configure do
config.action_dispatch.rescue_responses.merge!(
'AuthorizationException' => :unauthorized
)
# ...
end
Or simply use rescue_from
.
Solution 2
I'm guessing the reason Rails didn't introduce this exception is because Authorisation and Authentication is not Rails native behavior (not considering basicauth of course).
Usually these are responsibilities of other libraries Devise for NotAuthenticated; Pundit, Dude Policy, CanCanCan, Rollify for NotAuthorized) I would actually argue it may be a bad thing to extend ActionController
with custom exceptions like ActionController::NotAuthorized
(because like I said it's not it's responsibility)
So Way how I usually tackled this problem is that I've introduced custom exceptions on ApplicationController
class ApplicationController < ActionController::Base
NotAuthorized = Class.new(StandardError)
# ...or if you really want it to be ActionController
# NotAuthorized = Class.new(ActionController::RoutingError)
rescue_from ActiveRecord::RecordNotFound do |exception|
render_error_page(status: 404, text: 'Not found')
end
rescue_from ApplicationController::NotAuthorized do |exception|
render_error_page(status: 403, text: 'Forbidden')
end
private
def render_error_page(status:, text:, template: 'errors/routing')
respond_to do |format|
format.json { render json: {errors: [message: "#{status} #{text}"]}, status: status }
format.html { render template: template, status: status, layout: false }
format.any { head status }
end
end
end
Therefore in my controllers I can do
class MyStuff < ApplicationController
def index
if current_user.admin?
# ....
else
raise ApplicationController::NotAuthorized
end
end
end
This clearly defines that the layer your expecting this exception to be raised and caught is your application layer, not 3rd party lib.
The thing is that libraries can change (and yes this means Rails too) defining exception on a 3rd party lib classes and rescuing them in your application layer is really dangerous as if the meaning of exception class changes it brakes your rescue_from
You can read lot of articles where people are Waring about Rails raise
- rescue_from
being the modern goto
(now considering anti-pattern amongst some experts) and in certain extend it is true, but only if you are rescuing Exceptions that you don't have full control off !!
That means 3rd party exceptions (including Devise and Rails to certain point). If you define the exceptions classes in your application, you are not relaying on 3rd party lib => you have full control => you can rescue_from
without this being an anti-pattern.
Rick
Hi! I'm Rick Carlino, a software dev from the Chicago suburbs. I started programming professionally in 2012 and have been tinkering with computers since my first Windows 3.1 machine in the '90s. I am a senior software engineer at Qualia Labs. I also co-founded Fox.Build, one of the largest makerspaces in the Chicago suburbs. It's a community-led organization for engineers, artists and entrepreneurs or anyone who likes to make things. In the past, I've worked on projects for medical payment processing, IoT startups, textile manufacturers, hospitality management services, non-profits, the military, and a list of consulting clients that are too numerous to name. My public work is available on my Github page and blog. Outside of work, I enjoy long-distance running, pottery and studying the history of computing (1983-2003). My preferred languages are Typescript, Ruby, Elixir, QBasic, Esperanto.
Updated on November 12, 2021Comments
-
Rick about 2 years
I am writing an application that uses plain old Ruby objects (POROs) to abstract authorization logic out of controllers.
Currently, I have a custom exception class called
NotAuthorized
that Irescue_from
at the controller level, but I was curious to know: Does Rails 4 already come with an exception to indicate that an action was not authorized? Am I reinventing the wheel by implementing this exception?Clarification: The
raise AuthorizationException
is not happening anywhere inside of a controller, it is happening inside of a completely decoupled PORO outside of the controller. The object has no knowledge of HTTP, routes or controllers. -
Rick over 9 yearsThank you for answering my question. I am curious how exceptions are mapped with Mongoid as the ORM.
-
Luke Griffiths almost 8 years@Stefan's link references a specific line number in
master
which changes over time. Here's a more "perma" link to that code: github.com/mongoid/mongoid/blob/v4.0.2/lib/mongoid/…