There are a lot of questions on here that deal with the mechanics of authentication and authorization of RESTful APIs but none of them appear to go in to details of how to implement secure services at the application level.
For example let's say that my webapp (I've got Java in mind but this applies to any backend really) has a secure authentication system that allows users of the API to login with a username and password. When the user makes a request, at any point during the request processing pipeline I can call a getAuthenticatedUser()
method which will return either the null user if the user isn't logged in, or a User domain object that represents the logged in user.
The API allows authenticated users to access their data e.g. a GET to /api/orders/
will return that user's list of orders. Similarly, a GET to /api/tasks/{task_id}
will return data relating to that specific task.
Let's assume that there are a number of different domain objects that can be associated with a user's account (orders and tasks are two examples, we could also have customers, invoices etc.). We only want authenticated users to be able to access data about their own objects so when a user makes a call to /api/invoices/{invoice_id}
we need to check that the user is authorized to access that resource before we serve it up.
My question is then, do patterns or strategies exist to deal with this authorization problem? One option I'm considering is creating a helper interface (i.e. SecurityUtils.isUserAuthorized(user, object)
), which can be called during request processing to ensure that the user is authorized to fetch the object. This isn't ideal as it pollutes the application endpoint code with lots of these calls e.g.
Object someEndpoint(int objectId) {
if (!SecurityUtils.isUserAuthorized(loggedInUser, objectDAO.get(objectId)) {
throw new UnauthorizedException();
}
...
}
…and then there's the question of implementing this method for every domain type which could be a bit of a pain. This might be the only option but I'd be interested to hear your suggestions!
Best Answer
Please for the love of God do not create a
SecurityUtils
class!Your class will become 10k lines of spaghetti code in a matter of months! You would need to have an
Action
type (create, read, update, destroy, list, etc.) passed into yourisUserAuthorized()
method, which would quickly become a thousand-line-longswitch
statement with increasingly complex logic that would be difficult to unit test. Don't do it.Generally what I do, at least in Ruby on Rails, is have each domain object be responsible for its own access privileges by having a policy class for each model. Then, the controller asks the policy class if the current user for the request has access to the resource or not. Here's an example in Ruby, since I've never implemented anything like it in Java, but the idea should come across cleanly:
Even if you have complex nested resources, some resource must 'own' the nested resources, so the top-level logic inherently bubbles down. However, those nested resources need their own policy classes in the event that they can be updated independently of the 'parent' resource.
In my application, which is for my university's department, everything revolves around the
Course
object. It's not aUser
-centric application. However,User
s are enrolled inCourse
, so I can simply ensure that:@course.users.include? current_user && (whatever_other_logic_I_need)
for any resource that a particular
User
needs to modify, since almost all resources are tied to aCourse
. This is done in the policy class in theowns_whatever
method.I haven't done much Java architecture, but it seems that you could make a
Policy
interface, where the different resources that need to be authenticated must implement the interface. Then, you have all the necessary methods which can become as complex as you need them to be per domain object. The important thing is to tie the logic to the model itself, but at the same time keep it in a separate class (single responsibility principle (SRP)).Your controller actions could look something like: