The reasoning is sound and it is worth knowing, because it is the same reasoning that applies to every state-changing endpoint you own.
GET requests are supposed to be safe — meaning they do not change anything. That is not a stylistic preference, it is what the rest of the web assumes about them. Browsers prefetch links. Antivirus and mail scanners follow links in messages. Chat clients fetch links to build previews. Crawlers request every link they find. Accelerators speculatively load links you are hovering over.
A logout that happens on GET means any of those can log your users out, and none of it involves an attacker. The classic version is a third-party page embedding an image whose source is your logout URL: every visitor with a session on your site gets logged out on page load. Harmless in isolation, extremely annoying in practice, and it is a real thing that happens.
More importantly it is a category error that gets dangerous when it is applied to something other than logout. If logout can be triggered by a link, so can anything else built the same way — and the next endpoint is a delete.
A POST cannot be triggered by an image tag or a prefetch, and it carries the CSRF token, so the request has to come from your own page.
Converting the links. The mechanical change is to replace each anchor with a small form that posts to the logout URL and carries the CSRF token, with a button inside it.
Two practical notes:
- Do not write that form thirty times. Put it in a partial or an include and reference it everywhere. You will want to restyle it exactly once, and you will want one place to change if the endpoint moves.
- Style the button to look like your existing link if the navigation depends on it. A button carries default browser styling that will not match; reset it, and keep it a real button rather than a link with a click handler, so it still works with keyboard navigation and without JavaScript.
If the upgrade has blocked you and you need the site working now, the temporary escape is to keep a GET-accepting route of your own that renders a confirmation page with the POST form on it. The link goes to a page, the page does the POST. That is arguably the nicest end state anyway — an accidental logout becomes a confirmation screen rather than a lost session — and it means the link in your navigation stays a link.
While you are in there, this is a good moment to check the rest of the application for the same shape: anything that changes state behind a plain link. Deletes are the usual finding, often in an admin area where somebody thought it did not matter. Prefetching does not care that it is an admin area.