Is the current user allowed to perform a certain action on a certain resource? This is the central question of “resource-level” enforcement.
- Can a user update settings for this organization?
- Can a user read this repository?
- Can an admin resend a password reset email for this user?
Resource-level enforcement is the bread-and-butter of application authorization. If you only perform one type of authorization in your app, it should be this. Just about every endpoint in your application should perform some kind of resource-level enforcement.
Authorize an action
The method to use for resource-level authorization is called
Oso.authorize. You use this method to ensure that
a user has permission to perform a particular action on a particular resource.
authorize method takes three arguments,
It doesn’t return anything when the action is allowed, but throws an error when
it is not. To handle this error, see
oso.authorize(user, "approve", expense)
authorize method checks all of the
allow rules in your policy and
ensures that there is an
allow rule that applies to the given user,
action, and resource, like this:
allow(user: User, "approve", _expense: Expense) if user.admin?;
Let’s see an example of
authorize from within an endpoint:
def approve_expense(user, expense_id) expense = db.fetch( "SELECT * FROM expenses WHERE id = ?", expense_id) oso.authorize(user, "approve", expense) # ... process request end
As you can see from this example, it’s common to have to fetch some data before performing authorization. To perform resource-level authorization, you normally need to have the resource loaded!
What happens when the authorization fails? That is, what if there is not an
allow rule that gives the user permission to perform the action on the
resource? In that case, the
authorize method will raise
Oso::AuthorizationError. There are actually two types of authorization
errors, depending on the situation.
Oso::NotFoundErrorerrors are for situations where the user should not even know that a particular resource exists. That is, the user does not have
"read"permission on the resource. You should handle these errors by showing the user a 404 “Not Found” error.
Oso::ForbiddenErrorerrors are raised when the user knows that a resource exists (i.e. when they have permission to
"read"the resource), but they are not allowed to perform the given action. You should handle these errors by showing the user a 403 “Forbidden” error.
Note: a call to
authorize with a
"read" action will never raise a
ForbiddenError error, only
NotFoundError errors—if the user is not allowed to read
the resource, the server should act as though it doesn’t exist.
You could handle these errors at each place you call
authorize, but that would
mean a lot of error handling. We recommend handling
errors globally in your application, using middleware or something similar.
Ideally, you can perform resource-level authorization by adding a single line of
code to each endpoint.
As an example, here’s what a global error handler looks like in a Rails app:
class ApplicationController < ActionController::Base rescue_from Oso::ForbiddenError, with: :forbidden_error rescue_from Oso::NotFoundError, with: :not_found def forbidden_error render 'forbidden', status: 403 end def not_found_error render 'not found', status: 404 end end
Then, when your controllers call
authorize, they don’t need to worry about
handling the errors.
Connect with us on Slack
If you have any questions, or just want to talk something through, jump into Slack. An Oso engineer or one of the thousands of developers in the growing community will be happy to help.