Theory

What CSRF is, and why a two-part defense

Your forms POST happily right now — and that's a vulnerability. This lesson closes it, and in doing so you'll hit the single most misread error in Go web development. We're going to make you hit it on purpose, because debugging it is how you actually learn what CSRF protection does.

The attack. You're logged into SignFlow (your session cookie is in the browser). You visit some unrelated site — a forum, an ad. That page contains:

<form action="https://signflow.app/logout" method="post" id="x"></form>
<script>document.getElementById('x').submit()</script>

Your browser submits it — and automatically attaches your SignFlow session cookie, because that's how cookies work. SignFlow sees a valid session and logs you out. Swap /logout for a "change my email" endpoint and it's an account takeover. That's Cross-Site Request Forgery: a different origin causing an authenticated action using your ambient cookie. The user never clicked anything on SignFlow.

The defense has two halves, and this is the part everyone half-learns.

  1. A secret token. SignFlow puts a random token in a cookie and requires it echoed back in a hidden form field on every POST. The attacker's page can't read your cookie (SameSite/HttpOnly help) and can't guess the token, so it can't include the matching field. Real SignFlow forms carry it; forged ones don't.
  2. An Origin/Referer check. On top of the token, the middleware checks that the request came from SignFlow's own origin. This is the half people forget — and it's exactly the half that's about to bite you.

We use gorilla/csrf. One middleware, csrf.Protect, enforces both halves on every unsafe method. Forms embed the token via a hidden gorilla.csrf.Token field.

The catch you're about to meet. gorilla/csrf assumes it's serving over HTTPS. For the Origin/Referer half, it demands the request prove it came from the same secure origin — and a plain http://localhost request can't provide that proof. So the instant you turn on protection, your dev forms will be rejected with a message that explains none of this. We'll turn it on, watch it break, and only then fix it — because the fix only makes sense once you've felt the problem.

This lesson:

  1. Add csrf.Protect (config gains a secret, main derives the key) — and submit a form to hit the 403.
  2. Diagnose it, add the dev-only plaintext exemption, add the token field to every form, and watch it work.