The Kladde now knows who is signed in and what this person may do. Two attacks get through anyway if you're not careful. In the first, another site gets the browser of a signed-in person to send a request to the Kladde: the cookie travels along, and the Kladde takes the request for genuine. In the second, someone smuggles script into a page of the Kladde through an input field, and it then runs in the browser of another person. Neither attack exploits a hole in the sign-in; they exploit the trust that browser and app give each other.

In this lesson you attack both gaps yourself in a lab, close them and back that up with tests. After that the Kladde gets security headers that tell the browser what it may allow on its pages.

Forged requests (CSRF)

Ada is signed in to the Kladde, and her browser has the cookie kladde.signin. In another tab she opens a page with a prize draw. On it is a button “Claim prize” that is really a form:

HTML
<form method="post" action="https://kladde.sampleoffice.example/lab/1/done">
  <input type="hidden" name="reason" value="done">
  <button type="submit">Claim prize</button>
</form>

If Ada clicks, her browser sends a POST to the Kladde. Whether the cookie travels along is decided by its attribute SameSite (lesson 2.6.1). With Lax, the browser doesn't send it along with a POST from a foreign site. That protects a lot, but not everything:

  • Site doesn't mean address. For SameSite, kladde.sampleoffice.example and blog.sampleoffice.example belong to the same site, and so do localhost:5180 and localhost:5299. Whoever controls a page on another subdomain of the same company, such as a forgotten blog, is “same site,” and the cookie travels along.
  • Not every browser is new. Older browsers don't know SameSite or implement it differently.

This attack is called cross-site request forgery (CSRF), a forged request across the boundary of a site. The defense is an antiforgery token: a secret that the Kladde writes into each of its forms and demands back on submission. It has two parts, a cookie and a hidden field __RequestVerificationToken, which belong together cryptographically. The other site can make the browser send the cookie along, but it can't know the field: the browser doesn't let it read any page of the Kladde (same-origin policy). Without a matching field, the Kladde rejects with 400 Bad Request.

In ASP.NET Core it looks like this:

  • AddRazorComponents() registers the services for antiforgery too, and app.UseAntiforgery() checks. You've had both since lesson 2.5.1.
  • Blazor pages demand a valid token for every POST. EditForm writes the field itself; a plain <form method="post"> needs <AntiforgeryToken />.
  • Minimal API endpoints that read something from a form ([FromForm], IFormFile, IFormCollection) check the token automatically. .DisableAntiforgery() turns that off. That's right only for endpoints that serve no browsers at all and prove their requests some other way, such as a webhook with a signature of its own. Then write the reason next to it as a comment.
  • An endpoint that reads nothing from the form checks nothing either, even if the form sends a token along. That's what hits the Kladde in exercise 3.
  • JSON endpoints like the Kladde's API need no token. A foreign form can't send JSON, and a request with application/json from another site requires the Kladde's consent beforehand in the browser (CORS), which it never gives. A form to /api/contacts therefore hits no endpoint; in the Kladde, the public MapFallback from lesson 2.6.3 answers with 404.