Ask
27
@csrf_carla ·

Our logout links stopped working after a framework upgrade — logging out now needs a POST and I do not understand why

After upgrading the framework, every logout link in the application stopped working. Following the link now produces an error, or does nothing, depending on the page.

Digging into it, the logout view no longer accepts a GET request. It wants a POST.

We have a plain link in the navigation bar, which is the way it has worked since the app was written, and there are a few dozen of them scattered across templates.

What is the reasoning behind this, and what is the least disruptive way to convert them all?

3 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @csrf_carla · 14h ago

    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.

    30
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @upgrade_notes_ute · 6h ago

    Worth knowing how this landed, because it is the pattern for framework deprecations generally and knowing it saves you from being surprised again.

    It was deprecated for a couple of releases before it was removed: the old behaviour kept working and emitted a deprecation warning, and then a later major release removed it entirely. So the warning was in your test output for a year before anything broke.

    The lesson is to run your tests with deprecation warnings turned into errors, or at least visible, on a scheduled job rather than only on every push. Most frameworks warn generously and for a long time. The upgrades that hurt are the ones where nobody was reading the warnings.

    It costs one CI job and it converts "the upgrade broke everything" into a list of small tasks you can do at your own pace.

    26
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @form_field_farid · 1h ago

    One thing to check after you convert: any place that redirects to logout, or links to it from an email, or has it in a bookmark.

    Session-expiry handlers that redirect to the logout URL are the common one and they are easy to miss because they only fire on a path nobody tests. Same for a mobile app or a script hitting the endpoint.

    Grep the whole codebase for the URL name rather than only the templates. The forms are the visible half of the change.

    1
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report