10

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!

HJCee
  • 165
  • 1
  • 9

3 Answers3

10

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 your isUserAuthorized() method, which would quickly become a thousand-line-long switch 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:

class OrderPolicy

    class Scope < Struct.new(:user, :scope)

        def resolve

            # A user must be logged in to interact with this resource at all
            raise NotAuthorizedException unless user

            # Admin/moderator can see all orders
            if (user.admin? || user.moderator?)
                scope.all
            else
                # Only allow the user to see their own orders
                scope.where(orderer_id: user.id)
            end
        end
    end

    # Constructor, if you don't know Ruby
    def initialize(user, order)
        raise NotAuthorizedException unless user
        @user = user
        @order= order
    end

    # Whitelist what data can be manipulated by each type of user
    def valid_attributes
        if @user.admin?
            [:probably, :want, :to, :let, :admin, :update, :everything]
        elsif @user.moderator?
            [:fewer, :attributes, :but, :still, :most]
        else
            [:regualar, :user, :attributes]
        end
    end

    # Maybe restrict updatable attributes further
    def valid_update_attributes
    end

    # Who can create new orders
    def create?
        true # anyone, and they would have been authenticated already by #initialize
    end

    # Read operation
    def show?
        @user.admin? || @user.moderator? || owns_order
    end

    # Only superusers can update resources
    def update?
        @user.admin? || @user.moderator?
    end

    # Only admins can delete, because it's extremely destructive or whatever
    def destroy?
        @user.admin?
    end

    private

    # A user 'owns' an order if they were the person who submitted the order
    # E.g. superusers can access the order, but they didn't create it
    def owns_order
        @order.orderer_id == @user.id
    end
end

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 a User-centric application. However, Users are enrolled in Course, 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 a Course. This is done in the policy class in the owns_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:

public List<Order> index(OrderQuery query) {

    authorize(Order.class)
    // you should be auto-rescuing the NotAuthorizedException thrown by
    //the policy class at the controller level (or application level)

    // if the authorization didn't fail/rescue from exception, just render the resource
    List<Order> orders = db.search(query);
    return renderJSON(orders);
}

public Order show(int orderId) {

    authorize(Order.class)
    Order order = db.find(orderId);
    return renderJSON(order);
}
Vincent Savard
  • 1,906
  • 1
  • 17
  • 16
Chris Cirefice
  • 2,984
  • 4
  • 17
  • 37
1

A more convenient solution is to use annotations to mark methods which require some form of authorization. This stands out from your business code and can be handled by Spring Security or custom AOP code. If you use these annotations on your business methods rather than endpoints, you can be sure to get an exception when an unauthorized user tries to call them regardless of the entry point.

Michał Kosmulski
  • 3,474
  • 19
  • 18
  • Not what I'm asking. Spring annotations allow you to guarantee that the user has a particular authorization level (e.g. that the user is an admin) but I don't believe they help restrict access to particular entities. Let's say the business logic fetches and returns an invoice based on an id. I log in and provide someone else's invoice id to the endpoint. I don't believe the Spring annotations prevent horizontal access of that form? – HJCee Aug 01 '16 at 15:11
  • 1
    @HJCee Spring Securities AOP annotations are [quite expressive](http://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html). You can f.e. define a `@PreAuthorization("hasRole('ADMIN') and #requestingUser.company.uuid == authentication.details.companyUuid")` annotation where the `#requestingUser` segment references a pased object with the fieldName `requestingUser` which has a method `getCompany()` which returned object has a further method `getUuid()`. The `authentication` refers to the `Authentication` object stored in the security context. – Roman Vottner Aug 02 '16 at 01:39
  • 1
    @RomanVottner What happens if you need really complex authorization? for instance, only moderators on Stack Exchange with X gold badges in Y tag can make edits to delete questions (or whatever). I'll pass on the 300-character single-line annotation. – Chris Cirefice Aug 04 '16 at 07:50
  • 1
    @ChrisCirefice Either use [`@PreAuthorize("hasPermission(#user, 'allowDoSomething')")`](http://www.borislam.com/2012/08/writing-your-spring-security-expression.html) and implement your custom permission evaluator or write a [custom expression handler and root](http://www.borislam.com/2012/08/writing-your-spring-security-expression_9.html). If you want to alter the behavior of available annotations hava a look at [this thread](http://www.borislam.com/2012/08/writing-your-spring-security-expression_17.html) – Roman Vottner Aug 04 '16 at 11:03
0

Use capability-based security.

A capability is an unforgeable object that acts as evidence that one can perform a certain action. In this case:

  • Make each role (set of allowed actions) be an interface.
  • Have the operations that require authentication be methods on their respective interfaces. These should throw an exception if the receiver is not the current user of the request, if possible.

This makes it impossible to try to do something that the current user is not authorized to do.

That way it is impossible

Demi
  • 826
  • 7
  • 18
  • 1
    Not to be the critic, but isn't this a TL;DR of my answer? If yes, it would be preferred to simply comment on my answer instead of writing your own :) – Chris Cirefice Aug 15 '16 at 15:16
  • Not quite. The idea here is to express the different roles that users can have in the Java type system, such that you can't call a method on a user that requires a privilege the user doesn't have. – Demi Aug 15 '16 at 16:00
  • Further to Chris' comment, my question isn't about role-based access restriction (which is trivial to implement with any good web framework) but about access restrictions based on associations between users and data ('is object X owned by user Y' is a really simple example of such an association but they could be very complex). That's the problem I'm really trying to get advice about. – HJCee Aug 16 '16 at 08:32