9

I am having a hard time to deal with one particular issue in API design that might be very common. Let me give the concrete example I have: I am writing a RESTFul API for a store and have the situation that there is a /inventory resource to obtain the inventory. The problem is that there are two contexts in which this could happen:

  1. For the actual software and dashboard, the owners want to manage the inventory. There are items that they may set to inactive and should not appear to the public. But nevertheless, they must seem them, so that they can decide do activate, etc. From this perspective /inventory should return the complete inventory.

  2. For the shop frontend, people should see only available items. So the inactive items, or other stuff that is primarily for administration purpose should not be there.

So it seems /inventory should have two behaviors and I don't know what is the correct way to model this in a RESTFul API. I see three options:

  1. This is the one I picked, and I don't like it because I have a feeling it looks unnatural. I have that /inventory is public and returns only what the general public should see. Then /inventory/items is protected and returns everything. So the dashboard called /inventory/items and builds the inventory on the frontend for administration.

  2. The second option is to have two endpoints /admin/inventory and /inventory. I feel it is a bit weird though, because /admin is not a resource. So it is mixing up resource and credentials somehow.

  3. The APIs should be different. This is kind of a realization that there are distinct bounded contexts as in the DDD approach: inventory means different things for the administration of the operation and for the customers. So we should have two distinct APIs: one with endpoint like admin.store.com/inventory that has the full capabilities of editing, etc, and one public.store.com/inventory that has just the queries.

So: when a resource has two distinct behaviors and return schemas, depending on credentials and context, what is the correct way to deal with it in a RESTFul API?

2
  • 1
    "I feel it is a bit weird though, because /admin is not a resource." - just because /admin/inventory is a resource does not mean that a resource /admin needs to exist Commented 2 days ago
  • We use roles extensively for business logic, it is both a mess that I wouldn't recommend as well as a necessity for our use case. Or rather, it's probably possible to do "properly", but using roles is the easiest solution to managing lots of interconnected resources that may or may not be linked in the response. It's a trade-off. As far as REST is concerned I don't think there's a correct answer. One thing to consider that I don't see yet is routing, you can have your separate endpoints in the backend while using a shared one from the client if your routing layer has support for roles. Commented 2 days ago

5 Answers 5

16

Ooof I'm just going to chime in with the opposite answer. I'm not saying its completely wrong to have a single endpoint, but there are things to consider...

Try to avoid using the user role in business logic if possible and have two distinct endpoints.

  1. Show the active inventory

  2. Show all or inactive inventory

The reason this is better than having a single endpoint and examining the role to decide what to return is hard to explain. But if you go down the route of roles are part of business logic, it changes your security layer from being a "cross cutting concern" that you can cut out and handle at the endpoint access level, to an embedded parameter that might change any business logic.

If you keep the role at the endpoint access level and have two endpoints, you have much more flexibility, your business layer is completely isolated from your security layer.

  • You can have admins see only the active inventory on some pages and the full inventory on others.

  • Your tests don't have to worry about if the user is X or Y role

  • You can add new roles and give them whatever access your want

-- Adding some of the explanation from comments.

With security you are well advised to use a 3rd party out of the box solution rather than write your own. Normally these will plug into your web framework layer allowing you to annotate endpoints with roles or other criteria.

This means each end point can be considered a "permission" than can be assigned to a "role" in an off the shelf Role Based Authentication component.

If instead you pass the role into the business logic and let it conditionally decide what to do, you couple your BL to your security in a complex dependency relationship. You could be ten layers down in some object and find you need to know if the user is an Admin or not. And then worry about how other objects in the call chain have reacted to the user role. The simple definition of "permission" has been lost.

Your code is much less complex when the top level BL call can assume it has all the permissions required to fulfil its function if its been called.

I think the classic example is where you have a call on a website which does different things based on the user, but then you want to make the same call in bulk in an offline batch process which doesn't have a user. If you do the check in the application layer above the BL, it's easy to remove it from the batch process.

10
  • 1
    User identity is very often part of business logic. A customer should only be able to edit their own orders, not anyone elses. A user should see items in their "recommended" feed tailored to them, etc. Commented 2 days ago
  • 2
    id and role are not the same Commented 2 days ago
  • I'm just very suspicious of "we check access at the door so don't inside" as a security strategy. I'm also not convinced I understand "cross cutting concern" the way you do, because to me security as a cross cutting concern is a strike against your advice. Commented 2 days ago
  • Its about separating permissions and roles, rather than passing the roll into the logic you have permission, "see non-active" and assign it to the admin role. Commented 2 days ago
  • 4
    I think this answer is a good approach, but "naming things" becomes an issue. Instead of "show inactive inventory", I would prefer an endpoints split based on some root URL, like /inventory for customers, and /admin/inventory for admins. Honestly, though, roles bleed very easily into business logic, because user roles are often carved out of business requirements. I'm off on a tangent, and I admit that, but more and more I see "business rules" and "security" as the same thing. Commented 2 days ago
6

If the resource has always the same return schema and just needs different behaviours, in this case filters to be applied, I would recommend a query parameter: /inventory?list=all vs /inventory?list=active. (The second one might be the default behaviour for /inventory without a parameter).

For list=all or list=inactive you'd check admin permissions and reject the request with a 403 if not authorised.

If the responses in the two contexts actually have different return schemas, i.e. if only the admin one included the status and the links to item editor and preview, then indeed these should be separate endpoints (backed by different controllers). Whether you put them on different subpaths or on different subdomains does not really matter.

5

It is perfectly OK if users with different roles see different representations of the same resource, especially admin vs. ordinary users, which basically covers your case.

Another common case is that certain resources (for example orders in a web shop system) are accessible only to the customer (and the webshop owner) but not to other customers or unauthenticated visitors, even if they know the full resource URI.

So, if your /inventory lists active and inactive resources for the store owner, but only active items for customers, that would be perfectly reasonable. Of course, /inventory/<item-id> for an inactive item could then return a 404 status for customers (little white lie pretending that the item does not exist) and the item data for shop owners. One could argue that 403 is more suitable (item exists but you are not allowed to view it), and depending on the application that might be a better solution.

11
  • Thank you for the reply. Great, so from what you say I guess one way to implement it would be via claims on a JWT access token, so that the resource can detect what kind of user (admin, customer or simply not logged in, etc) and react appropriately returning the desired representation? Commented 2 days ago
  • 1
    @user1620696, yes, exactly that. Commented 2 days ago
  • 2
    404 is not a lie. The server looked in the places the customer is permitted, and did not find the item there Commented 2 days ago
  • 1
    I think the reason why treating securables as first-class addressables is useful, is because it's very much easier to talk about someone "having permission to use both /admin/inventory and /customer/inventory" than it is to talk about "having permission to call /inventory with the ?adminView parameter both being true and being false/missing". (2/2) Commented 2 days ago
  • 1
    @JimmyJames Rather than Cache-Control: no-store you may want to use Vary: Cookie or Vary: Authorization Commented 2 days ago
2

It seems to me that these two are not the same resource.

  1. Your shop's entire inventory. Analogous to the shop + what's in the back room.
  2. Your shop's available products for sale (catalog or listings).

From an API point of view, this could be two separate routes

/inventory → Protected for authorized persons only
/catalog → Available for anyone. Returns only inventory items which are present in the catalog.

Keep in mind that you don't need to model your REST interface (API) to be exactly like your database. Multiple resources can share database tables, or mix in other information.

1
  • This would be the approach I would use as I think it's just cleaner if endpoint return values are always consistent, regardless of who the user is (EDIT - and of course, the user's permissions affect what endpoint they can access). Commented yesterday
1

Most content management systems I have seen provide a technical backend UI to manage resources (as well as their meta data), and a user-friendly frontend to display nicely formatted web pages. So the views of a content admin and a frontend user on the same resources are very different in such a CMS. Here option 3 (individual APIs) seems to be most adequate if you want to design your system similar to such a CMS.

However, I guess there are also systems which behave more along the WYSIWYG philosophy and try to keep the admin's view very similar to a frontend user's view. That could make sense when the active/inactive flag is the only difference between the resource views, and no other meta data has to be maintained by an admin. If you want your system to be designed that way, option 1 seems to be more adequate, since it will make it a lot easier to develop a single UI which can be used by users and admins as well.

So ask yourself:

  • How different do you want those use cases to be, and how different do you need the views on the data to be in these two use cases?

The UI and use cases are where the requirements for you API are coming from - so that's where your requirements analysis has to start, and where your design decision should be based on. There is no braindead rule what the "correct way to deal with it in a RESTFul API" is.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.