1

I'm struggling with defining proper endpoints for a RESTful API, given the following requirements:

We have an existing /customers endpoint, which when called with the GET verb, returns a list of customers, which look like so:

{
  "id": "some-id",
  "name": "John doe",
  "email": "john.doe@example.com",
  "address": "Somestreet 1, 1234 Somecity, Somecountry",
  "phone": "+1 234 567 765"
}

I now have a requirement to introduce a new user type to our system, these users are allowed to query the /customers endpoint, but are not allowed to see the same data about the customers:

  • emails should be masked (e.g. j***.d**@example.com)
  • address should only contain city-level information (e.g. Somecity, Somecountry)
  • Phone numbers should not be visible at all

What is, in RESTful programming, the best way to deal with these requirements? I have thought about the following approaches:

Design a completely different resource

I could design another REST resource, which would live under another endpoint like /customers-basic, and provide exactly the model that is intended for the new user type. This doesn't feel maintainable though, in an environment that is considerably complexer than the very simple example I provided, what if you have 3,4 different user types that all need to see different information about customers, I would quickly be challenged with coming up with names for all the different resource types and their routes.

Return different response types

While it's perfectly possible (using ASP.NET 6 at least) to return different models for the same resource, I think this would not be considered correct use of the REST principles. It would also make it hard to make a public specification (OpenAPI 3 e.g.) which describes which model is returned in which scenario

Making the sensitive properties sub-resources

Another solution would be to remove all sensitive properties from the customer resource, and provide them as a sub-resource of the customer (e.g. /customers/{customer-id}/email). This would solve making certain properties completely unavailable to some users (returning a 403 if they requested this protected sub-resource), but it still wouldn't solve the masking of the email property. Also this solutions would require additional API calls from the client application to completely visualize the resource. If you would need these properties in a paginated list view that would exponentially increase the number of API calls you would have to do to a point where it could potentially start getting detrimental to the applications performance.

Set unavailable properties to null

This would not solve the masking issue, but it would be a solution to properties you would want to completely deny showing to the user with limited permissions. Although in the case of optional properties (phone number e.g.) you would not be able to differentiate between not having a phone number, and the number being hidden from you.

GraphQL

Slightly off-topic, but if a different protocol does provide a solution, it should be mentioned here. I don't know a lot about GraphQL, but I do know it provides means of specifying which properties are available to a client, and then the client specifying which properties of a resource it wants to see. What I couldn't find is if it's common practice to limit a subset of properties to a higher-permission enabled user.

Any other approaches I'm missing? How do other people deal with these kind of requirements?

FYI: I'm working in a ASP.NET 6 environment, but it's really more of a design question than a technology question.

3 Answers 3

4

REST does not define behavior for such fine-grained details. Keep the same endpoint, but return different data depending on the client. If the client has authorization to view the customer, return a 200 OK response. This satisfies REST and the HTTP protocol. The specific data being returned is up to application logic.

Consider for a moment that you create a second endpoint called /customers-basic and the existing /customers endpoint returns unmasked information. What is stopping a malicious actor from calling /customers when they should be calling /customers-basic? Forget that the client should call one over the other. Forget that you might give the client the endpoint they should call. Assume that some client will attempt to access information they are not authorized to see. Authorize every request. The data returned in the request depends on the use case and permissions. REST doesn't even figure into that decision or design. Consider another example where you and I are coworkers.

Let's say I work in level 1 tech support and you work in level 2 support. I take calls first and then escalate to you if I cannot handle the problem. You reply to me saying:

Hi Greg,

I was able to resolve this customer's issue:

https://example.com/customers/123

Alex

You can see that URL because you have elevated access to the system. With two different endpoints, I should visit /customers-basic/123 due to me having lower access. The data returned by both endpoints represents the same "customer" but you force clients to understand their access to the system in order to access that resource. You also force clients to understand each others access.

If I visit the URL in your email, I would get 401 Unauthorized response, which is a frustrating thing for clients to remember. It would drive clients crazy because it is the same customer.

Remember that a resource in RESTful programming is an abstract concept. Two different clients do not necessarily need the same data bit-for-bit. Conceptually a URL should return the same object, but the exact representation of that object will depend on application logic out of the scope of REST.

2
  • The issue of 401 unauthorized can be solved using a redirect to the basic scheme. Not arguing pro second resource type in particular, but this is a valid solution to the problem you pose.
    – Flater
    May 4 at 18:00
  • @Flater: the problem with a redirect is what to do about a client whose access has changed. In my tech support example, permanent redirects are problematic if I get promoted from level 1 to level 2 tech support. At that point, /customers/123 is the URL that I should see. A redirect to /customers-basic/123 is not desirable in that situation. Of course a temporary redirect can fix that issue. But this further reinforces the downsides of multiple end points representing the same object. It gets complicated. It will be complicated for clients or the server. Jun 21 at 12:22
1

what's the proper way to have different resource properties based on user authorization?

Technically, it doesn't matter very much. This is because caches have different rules for responses to authorized requests than they do for unauthorized requests, so unless your users are sharing a "private" cache, there's not likely to be a problem.

Resources can have multiple representations; we normally use the Vary header so that caches can review request metadata to see if the current request can be served using a previously cached response.

There is no need to send the Authorization field name in Vary because reuse of that response for a different user is prohibited by the field definition -- HTTP Semantics 12.5.5


But I would argue that, in much the same way that we might design different resources for different locales, or different data representations, we should also design different resources for different privilege levels, and then use a boring access control layer to ensure that access to the sensitive information is restricted.

However, we need to be aware that isn't necessarily free; once we introduce the idea of two resources, we have to be aware that the representations of those two resources will not necessarily agree with one another (being snapshots of the shared information at two different points in time).

For users that only ever look at one representation of the resource, that's probably fine.

But for users that see both the resource with lowered privilege and the resource with elevated privilege, the representations of those two resources won't always agree (because time passes); you may have to invest more in the design of the representation to reduce confusion.

(See also Immutability Changes Everything.)

0

I don’t think there is any particular rule. I would create a different endpoint, so that authorisation can be handled cleanly. So if your application thinks you should be allowed access to the complete data, you will be asked for that authorisation and the access succeeds, or you are not authorised and the access fails, instead of silently giving you less information.

This also allows your application to show you incomplete information when the complete information is not needed, even if you are authorised to see it. So we have the principle that like anywhere else, you either get the information requested, or you get nothing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.