1

I'm making a new RESTful API in Flask that should accept both GET (for requesting the resource) and PATCH (for performing various incremental, non-idempotent updates) for a given object. The thing is that some of the data that's patched in must be authenticated, and some shouldn't be.

To clarify this with an example, let's say I'm building an app that let's everyone query how many times a resource has been clicked on and how many times its page has been viewed. It also let's people do an update on the resource in javascript saying the resource was clicked again (unauthenticated, since it's coming from the front-end). It additionally let's an authenticated backend increment the number of times the page has been viewed.

So, following RESTful principles, I'm thinking all three actions should be done on the same path-- something like /pages/some_page_name which should accept both GET and PATCH and should accept two different kinds of data with PATCH. The problem is that in Flask, it looks like authentication is always done with a decorator around a method, so if I have a method like @app.route('/pages/<page_id>', methods=['GET', 'PATCH']), my authentication would be done with a decorator like @auth.login_required for that whole method, which would force even the methods that don't require authentication to be authenticated.

So, my question is three-fold:

  1. Am I right in structuring all three actions mentioned under the same path/ is this important?
  2. If I am right, and this is important, how do I require authentication only for the one type of PATCH?
  3. If this is not important, what's a better or simpler way to structure this API?
Eli
  • 36,793
  • 40
  • 144
  • 207

1 Answers1

1

I see several problems with your design.

let's say I'm building an app that let's everyone query how many times a resource has been clicked on and how many times its page has been viewed

Hmm. This isn't really a good REST design. You can't have clients query select "properties" of resources, only the resources themselves. If your resource is a "page", then a GET request to /pages/some_page_name should return something like this (in JSON):

{
    'url': 'http://example.com/api/pages/some_page_name',
    'clicks': 35,
    'page_views': 102,
    <any other properties of a page resource here>
}

It also let's people do an update on the resource in javascript saying the resource was clicked again

"clicking something" is an action, so it isn't a good REST model. I don't know enough about your project so I can be wrong, but I think the best solution for this is to let the user click the thing, then the server will receive some sort of a request (maybe a GET to obtain the resource that was clicked?). The server is then in a position to increment the clicks property of the resource on its own.

(unauthenticated, since it's coming from the front-end).

This can be dangerous. If you allow changes to your resources from anybody, then you are open to attacks, which may be a problem. Nothing will prevent me from looking at your Javascript and reverse engineering your API, and then send bogus requests to artificially change the counters. This may be an acceptable risk, but make sure you understand this may happen.

It additionally let's an authenticated backend increment the number of times the page has been viewed.

Backend? Is this a client or a server? Sounds like it should be a client. Once again, "incrementing" is not a good match for REST type APIs. Let the server manage the counters based on the requests it receives from clients.

Assuming I understand what you are saying, it seems to me you only need to support GET. The server can update these counters on its own as it receives requests, clients do not need to bother with that.

UPDATE: After some additional info provided in the comments below, what I think you can do to be RESTful is to also implement a PUT request (or PATCH if you are into partial resource updates).

If you do a PUT, then the client will send the same JSON representation above, but it will increment the corresponding counter. You could add validation in the server to ensure that the counters are incremented sequentially, and return a 400 status code if it finds that they are not (maybe this validation is skipped for certain authenticated users, up to you). For example, starting from the above example, if you need to increment the clicks (but not the page views), then send a PUT request with:

{
    'url': 'http://example.com/api/pages/some_page_name',
    'clicks': 36,
    'page_views': 102
}

If you are using PATCH, then you can remove the items that don't change:

{
    'clicks': 36
}

I honestly feel this is not the best design for your problem. You have very specific client and server here, that are designed to work with each other. REST is a good design for decoupled clients and servers, but if you are on both sides of the line then REST doesn't really give you a lot.

Now regarding your authentication question, if your PUT/PATCH needs to selectively authenticate, then you can issue the HTTP Basic authentication exchange only when necessary. I wrote the Flask-HTTPAuth extension, you can look at how I implemented this exchange and copy the code into your view function, so that you can issue it only when necessary. I hope this clarifies things a bit.

Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
  • It's a contrived example. Of course it's always better to do these things in a secure, authenticated way on the backend, but I'm unable to do that in my use case. I've given a simplified example that has the same difficulties as my use case. To better make the point, think of a service like MixPanel that can support both authenticated calls from a backend, as well as unauthenticated calls from JavaScript. My use case is similar, except both calls are on the same resource-- just different attributes of said resource. – Eli Jan 03 '15 at 21:19
  • As for the comments about clicks not being a good fit for a restful API: imagine a situation where you wanted to record these cross domain. Why wouldn't you use a RESTful API to record these? What would you use instead? In general, I'm letting outside clients talk to a service I provide, and a RESTful API seems like the best fit for that to me. If you disagree, I'd love to hear your argument. – Eli Jan 03 '15 at 21:21
  • @Eli: it's really hard to give you advice when it turns out you are simplifying your situation too much. On supporting unauthenticated requests, I did not say it is wrong, just warned you that there are risks, in case you weren't aware of them. If I was doing something like this, I would at least have the client authenticate (not the user operating the client), but that is entirely up to you, so I can't really say unauthenticated calls are wrong, If I gave you the impression that they are then I should have been more clear, they are not wrong. – Miguel Grinberg Jan 03 '15 at 23:36
  • Regarding the clicks, I still don't understand. Is the cross-domain a real situation or are you trying to come up with examples? Let's say you have domains A and B. These pages are hosted by A, and your API is hosted by B. The Javascript code that A serves with the pages clearly knows about B, so the A server can easily keep track of views and pass them to B without the need to use a REST API. – Miguel Grinberg Jan 03 '15 at 23:39
  • I'm just looking for advice on how to structure a service that provides the features I asked for in a RESTful manner, specifically in terms of forcing authentication for some PATCH calls but not for others, specifically in Flask. I appreciate your advice about why everything I want to do is a bad idea, but all I want to know is how to do it. I hope this doesn't come off as terse or mean. – Eli Jan 04 '15 at 00:33
  • As for the clicks, remember we're talking about a service provided like MixPanel, so in your example the A server would be a customer website who is just serving some JavaScript I'm providing. I don't have access to the A server's backend, so I can't force that to send me information. – Eli Jan 04 '15 at 00:36
  • @Eli: I guess we agree on the GET implementation, correct? This is how your Javascript client gets the clicks and the page views. The problem is that there is no way to "increment a counter" within REST, all you can do is update the resource that holds these counters (i.e. send updated values for them). I would not use a REST solution for this problem, since it seems the client and the server are very specific (i.e. written by you) and you do not expect other clients to talk to your API server. Regardless, I'll complete my answer with the REST way to implement this. – Miguel Grinberg Jan 04 '15 at 03:51