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.