Wednesday, March 29, 2017

Ignore or not API endpoint parameters based on access level

Leave a Comment

I am working on an API endpoint that returns a list of products:

"api/products" 

The endpoint accepts the following parameters:

page_size page_number  

Each product has a boolean property named IsApproved.

In the web application used by common users I always want to return only the Approved products ... On the web ADMIN application used by administrators I want to return all products, Approved or Not ...

My idea would be to add a new parameter (enumeration) named:

ApprovedStatus 

And the values would be Approved, NotApproved and All.

On each API call I would check the user permissions ... If is admin I will consider the value on this parameter. If not then I will always return only approved products.

Another solution would be to have different endpoints ...

Any advice on which approach to take or is there other options?

4 Answers

Answers 1

You're right about your ideas:

  1. You can create a new endpoint just for admins, that will return all products
  2. You can use a kind of authorization (e.g. Authorization Header) in order to check if the API is being called through admin or normal user. Then you can route internally to get all products or just IsApproved products.

You can add a proxy in front of your API to route to the right action, but it can also be achieved directly in the API but I think the second solution is easier.

Answers 2

Adding one more property is a bad idea.

In my opinion, adding another end point is very good. Because it will increase the protection in the admin end point.

Otherwise, since it is a web application, Simply set a cookie and a session to identify and separate the admin and user.

Answers 3

The approval status is part of the product, therefore, in a perfect REST world, you don't want a different endpoint at all since you're accessing the same resource.

Then, for filtering a resource based on a property value, I think the convention is that if you specify that property as a query parameter it will only return those matching the value, and if not, it will return all of them, so I don't see the need to define a special ApprovedStatus parameter with some special values. Just query by isApproved!

Finally, about how to handle authorization. This, I think, should be handled at a completely separate layer**. If authorization is involved, you should have an explicit authorization layer that decides, for a specific resource and user, wether access is granted or not. This means the query would be triggered and if one of the resources generated by the query fails to be authorized for the user that triggered the query, it's taken out of the results. This accomplishes the behaviour you want without having any code that is checking specific users against specific query parameters, which is good because if tomorrow you have another endpoint that exposes this objects you won't have to implement the same authorization policy twice. Pundit is a perfect example on how to do this with Ruby elegantly.

**Of course, this approach retrieves data from the database unnecessarily which could matter to you, and also opens your endpoint up to timing attacks. Even then, I would consider tackling these problems premature optimizations and should be ignored unless you have a very good reason.

Answers 4

Going with the principle of least astonishment, I'd be in favour of adding a second endpoint for admin users. Such that you'll have:

GET /api/products       (for regular users) GET /api/admin/products (for admins) 

This allows your code and API documentation to be nicely separated, and all of the admin-specific authentication details can live under the "admin" namespace.

The intention behind each API call is also clearer this way, which helps developers; and means that you can differentiate between admin vs regular usage in any usage stats that you track.


With ApprovedStatus, I think the specifics here don't matter much, but - considering what a developer using the API might reasonably expect / assume - it would be good to:

  • Ensure the ApprovalStatus parameter name matches the property name for "approval" that you return with each product object
  • Defaults to "approved" if it is not specified
  • Alert the user when an invalid value is specified, or one that they don't have access to

Bottom line: to answer your headline question - I think it's bad practice to simply ignore user input which is invalid. Design your API such that distinctions around when input can be passed in is very clear; and always alert the user if you receive input values that are correct, but not in the way that the user has requested.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment