136

All over the internet, I see the following advice:

A GET should never change data on the server- use a POST request for that

What is the basis for this idea?

If I make a php service which inserts data in the database, and pass it parameters in the GET query string, why is that wrong? (I am using prepared statements, to take care of SQL Injection). Is a POST request in some way more secure?

Or is there some historic reason for this? If so how valid is this advice today?

Devdatta Tengshe
  • 2,514
  • 4
  • 21
  • 22
  • 37
    [There's a relevant DailyWTF entry](http://thedailywtf.com/Articles/WellIntentioned-Destruction.aspx). – Joachim Sauer Mar 01 '13 at 15:31
  • 3
    [Command/query separation.](http://en.wikipedia.org/wiki/Command%E2%80%93query_separation) – Mason Wheeler Mar 01 '13 at 18:33
  • Thank you for asking this question, and thank you @Oded for the well phrased answer I always needed a reference to send people who ask this question towards :) – Benjamin Gruenbaum Mar 01 '13 at 18:43
  • Also see HTTP PUT - http://stackoverflow.com/questions/630453/put-vs-post-in-rest (with notes about being idempotent) – Bratch Mar 01 '13 at 19:52
  • web crawlers use 'GET' (mostly, they are more judicious about there use of other request types). Do you want the state of your data changed because of some anonymous crawling robot that randomly went to a lot of links on your site? – Martin York Mar 02 '13 at 16:11
  • 2
    @JoachimSauer While GET would have saved them from the crawler, the root problem was lack of authentication. Any script kiddy could have POSTed them into oblivion as well. – CodesInChaos Mar 02 '13 at 16:20
  • Another DailyWTF for how dangerous it can be: [The Spider of Doom](http://thedailywtf.com/Articles/The_Spider_of_Doom.aspx) – Halil Özgür Mar 23 '13 at 14:04

6 Answers6

210

This is not advice.

A GET is defined in this way in the HTTP protocol. It is supposed to be idempotent and safe.

As for why - a GET can be cached and in a browser, refreshed. Over and over and over.

This means that if you make the same GET again, you will insert into your database again.

Consider what this may mean if the GET becomes a link and it gets crawled by a search engine. You will have your database full of duplicate data.

I also suggest reading URIs, Addressability, and the use of HTTP GET and POST.


There is also a problem with link prefetching in some browsers - they will make a call to pre-fetch links, even if not indicated so by the page author.

If, say, your log out is behind a "GET", linked from every page on your site, people can get logged out just due to this behaviour.

Oded
  • 53,326
  • 19
  • 166
  • 181
  • 37
    Many, many, many tools, utilities, web crawlers and other thingamajiggies assume that `GET` will never be a destructive action (rightly so, since it's specified this way). If you now break your application by breaking that specification, you'll get to keep both parts of your application. – Joachim Sauer Mar 01 '13 at 12:21
  • You may also never get the `GET` because it happens to be cached somewhere along the way. – Jan Hudec Mar 01 '13 at 12:33
  • 1
    how does the # views get changed on stack, if not by a get ? – NimChimpsky Mar 01 '13 at 14:18
  • 8
    @NimChimpsky: it does get changed by a `GET`. That advice is simply wrong. *Safe* means that the user cannot be held accountable for side-effects, not that there can't be side-effects. Otherwise you couldn't have log files for your server, which would be absurd! This is spelled out quite clearly in section 9.1.1 of RFC2616. – Jörg W Mittag Mar 01 '13 at 14:35
  • 8
    @JörgWMittag: I wouldn't say "simply wrong", I'd say "phrased inperfectly". A GET should not have a change as it's goal. **Of course** you're allowed to count, log and observe a GET request. But it should not modify your actual business data. – Joachim Sauer Mar 01 '13 at 14:54
  • 25
    @NimChimpsky A `GET` shouldn't change the _resource_ requested by the `GET`, but that doesn't mean 'nothing on the server should change'. Of course things like logs, counters, and other server state may change during any request. – Eric King Mar 01 '13 at 14:55
  • 10
    Quite some years ago, Google released a browser add-on (iirc) that would pre-fetch pages via links. This also happened on some control panels that were designed poorly - the URLs would cause a record or something to be written or even deleted on the server (think post?action=delete). This caused actions to be executed without the user knowing it. Google discontinued that addon for that reason, iirc, even if it was the webapp manufacturer's fault for using GETs to change state. – cthulhu Mar 01 '13 at 15:41
  • 1
    @JoachimSauer: When we unsubscribed all a web papers customers (by following the unsubscribe link on a page they were not very happy with us). Just because we did everything correctly does not mean that they think they have a bug (or want to invest in fixing it). – Martin York Mar 02 '13 at 16:15
  • Tracking counts/analytics via GET requests is a hacky approach to begin with. Much better to fire off a separate HEAD request to a handler/server designed specifically for that function. Else, you're unnecessarily mixing concerns at the expense of clean/solid modularity. – Evan Plaice Mar 03 '13 at 01:32
  • How about unsubscribe links in emails? They're `GET` requests and change state. It's safe since you can't unsubscribe multiple times and you can't use anything else. Shouldn't that specific use case be allowed? – mbillard Aug 12 '15 at 19:31
  • 1
    @mbillard What if your mailclient decides to fetch the link without you clicking on it? For example because it wants to scan it for malware. The proper approach for unsubscribe links is to lead to a page where the user can click a button to unsubscribe (where the button click triggers a POST request). – CodesInChaos Dec 28 '15 at 17:48
26

Each HTTP verb has it's own responsibility. For example GET, as defined by RFC

means retrieve whatever information (in the form of an entity) is identified by the Request-URI.

POST, on the other hand, means insert or more formally

The POST method is used to request that the origin server accept the
entity enclosed in the request as a new subordinate of the resource
identified by the Request-URI in the Request-Line

Reasons for keeping it this way:

  • It's very simple and works on the global Internet scale since 1991
  • Stick to the single responsibility principle
  • Other parties use GET to act as means of information retrieval and data mining
  • GET is assumed to be a safe operation that never modifies the state of the resource
  • Security considerations, GET is effectively a read, whereas POST is effectively a write
  • GET is cached by browsers, nodes in the network, Internet Service Providers
  • Unless the content changes, GET to the same URL must return same results to all the users or else you you won't have any trust what so ever in the returned result

For completeness and just to enforce correct usage (source):

  • GET parameters are passed as part of the URL, which is of small and limited length of 256 chars by default, with some servers supporting 4000+ chars. If you want to insert a long record, there is no legitimate way to pass this data in
  • W̶h̶e̶n̶ ̶u̶s̶i̶n̶g̶ ̶s̶e̶c̶u̶r̶e̶ ̶c̶o̶n̶n̶e̶c̶t̶i̶o̶n̶,̶ ̶s̶u̶c̶h̶ ̶a̶s̶ ̶T̶L̶S̶,̶ ̶U̶R̶L̶ ̶i̶s̶ ̶n̶o̶t̶ ̶g̶e̶t̶t̶i̶n̶g̶ ̶e̶n̶c̶r̶y̶p̶t̶e̶d̶,̶ ̶h̶e̶n̶c̶e̶ ̶a̶l̶l̶ ̶t̶h̶e̶ ̶p̶a̶r̶a̶m̶e̶t̶e̶r̶s̶ ̶o̶f̶ ̶̶G̶E̶T̶̶ ̶a̶r̶e̶ ̶t̶r̶a̶n̶s̶f̶e̶r̶r̶e̶d̶ ̶p̶l̶a̶i̶n̶ ̶t̶e̶x̶t̶. URL is actuall encrypted with TLS, so TLS is fine.
  • Inserting binary data or non-ASCII characters using GET is impractical
  • GET is re-executed if a user presses a Back button in a browser
  • Some older crawlers may not index URLs with a ? sign inside
oleksii
  • 1,196
  • 1
  • 9
  • 20
  • 1
    Are you sure that the URL is unencrypted over TLS? I was under the impression that the SSL/TLS handshakes occur prior to the HTTP headers being transferred. This is the reason why virtual hosting HTTPS sites over a single IP address is difficult. Am I mistaken? – Brandon Mar 02 '13 at 17:17
  • That's right, I fixed it – oleksii Mar 02 '13 at 20:14
  • 2
    @Brandon Modern browsers send the host domain in the clear as part of the TLS handshake (known as server name indication), to allow hosting more than one domain per IP address. The path/query part of the url is protected by TLS. There is no difference between GET and other HTTP verbs in that regard. – CodesInChaos Mar 02 '13 at 21:41
10

EDIT: Before, I said POST helps protect you against CSRF but this is wrong. I did not think this through correctly. You must require a session-scope unique hidden token in all your requests to change data to protect against CSRF.

In the early days of the internet there were browser accelerators. These programs would start clicking links on a page to cache the content. Google Web Accelerator was one of these programs. This could wreak havoc on an application that makes changes when a link is clicked. I would make the assumption that there are still people using accelerator software.

Proxy servers and browsers will cache GET requests so when the user accesses the page again it may not send the request to your application so the user thinks they took an action, but they really didn't.

Sarel Botha
  • 287
  • 1
  • 5
  • 1
    CSRF is equally possible with GET and POST. For example the attacker can include an auto-submitting form on their site to trigger a POST request. The standard approach to preventing CSRF is explicitly including a value unknown to the attacker in the request (unlike the implicitly includes cookie headers). – CodesInChaos Dec 28 '15 at 17:53
9

If I make a php service which inserts data in the database, and pass it parameters in the GET query string, why is that wrong?

The simplest answer is "because that's not what GET means."

Using GET to pass data for an update is like writing a love letter and sending it in an envelope marked "SPECIAL OFFER - ACT NOW!" In both cases, you should not be surprised the recipient and/or intermediaries mishandle your message.

Nathan Long
  • 3,667
  • 3
  • 24
  • 28
5

For your CRUD operations in a database-centric application use the following schema:

Use HTTP GET for Read Operations (SQL SELECT)

Use HTTP PUT for Update Operations (SQL UPDATE)

Use HTTP POST for Create Operations (SQL INSERT)

Use HTTP DELETE for Delete Operations (SQL DELETE)

  • 4
    Put vs post is not as you state. Put is for when the client is modifying the resource at the exact given location. For a post the server ultimately decides the exact Uri to the resource. – Andy Mar 20 '14 at 00:54
  • Isn't HTTP PUT more like a SQL DELETE and INSERT rather than UPDATE? Also SQL UPDATE can update many records at once, but HTTP PUT will update only one thing. – David Klempfner Aug 31 '18 at 01:39
  • PATCH is for UPDATE – GreenSaguaro Jan 06 '20 at 19:35
1

A GET should never change data on the server- use a POST request for that

That advice, and all of the answers here are wrong. Obviously I'm being overly dramatic, the other answers are excellent, but I believe the exact advice should be given as:

A GET should rarely change data on the server- use a POST request for that

To say "never" is too extreme, and although the other answers here accurately explain why you should "rarely" do it, there are some scenarios where it is perfectly reasonable to change data with a GET. An example is a one-time use email verification link. Typically these links contain a GUID that when accessed will have to change data. If correctly implemented subsequent identical GET requests will be ignored.

This is obviously an edge case, but certainly worth noting.

TTT
  • 236
  • 1
  • 5
  • 3
    What if your mailclient decides to fetch the link without you clicking on it? For example because it wants to scan it for malware. The proper approach for unsubscribe links is to lead to a page where the user can click a button to unsubscribe (where the button click triggers a POST request). – CodesInChaos Dec 28 '15 at 17:55
  • @CodesInChaos - excellent point! I agree with you. I have removed the unsubscribe example and have left the email verification as the sole example. There may be others besides email verification where a GET makes sense, but I can't think of any at the moment. – TTT Dec 28 '15 at 18:31
  • The problem with GET having side effects applies equally to email confirmation. Now the client following the link would confirm an account somebody else created using your email, allowing them to impersonate you. – CodesInChaos Dec 28 '15 at 18:35
  • @CodesInChaos - that one's a stretch. The impersonation you speak of would come from the same username or public personal name, not the same email address, and that can happen regardless of what email address they use (typically only the server knows the account holder's email address anyway). Besides, it would be pointless to create an account with someone else's email address. How could that help them? They could not control their own account. – TTT Dec 28 '15 at 19:18