Resource-level Enforcement

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. The authorize method takes three arguments, user, action, and resource. It doesn’t return anything when the action is allowed, but throws an error when it is not. To handle this error, see Authorization Failure.

oso.authorize(user, "approve", expense);

The 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.isAdmin;

Let’s see an example of authorize from within an endpoint:

public void approveExpense(User user, int expenseId) throws AuthorizationException {
    Expense expense = Expense.byId(expenseId);
    oso.authorize(user, "approve", expense);

    // ... process request
}

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!

Authorization Failure

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 an AuthorizationException. There are actually two types of authorization errors, depending on the situation.

  • NotFoundException errors 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.
  • ForbiddenException errors 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 ForbiddenException error, only NotFoundException 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 NotFoundException and ForbiddenException 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 exception handler looks like in a Spring MVC app:

import com.osohq.oso.Exceptions;

@ControllerAdvice
class GlobalControllerAuthorizationExceptionHandler {
    @ResponseStatus(HttpStatus.NOT_FOUND) // 404
    @ExceptionHandler(Exceptions.NotFoundException.class)
    public void handleOsoNotFound() {}

    @ResponseStatus(HttpStatus.FORBIDDEN) // 403
    @ExceptionHandler(Exceptions.ForbiddenException.class)
    public void handleOsoForbidden() {}
}

Then, when your application calls authorize, it will know how to handle exceptions that arise.

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.