Knowing that client-side validation is not security, and having internalised it, are different states. The gap closes the first time you bypass a form yourself.
Once you have edited a disabled field in devtools, replayed a request with a changed value, and watched a server accept it without complaint, “the frontend already checks that” stops sounding like a reason for anything.
Two jobs, not one
- In the browser: fast, friendly feedback. Catch the typo before the round trip. This is a user-experience feature.
- On the server: assume every field is hostile, because the request did not necessarily come from your form. This is the actual boundary.
They look like duplicate work. They are not; they have different threat models and different failure modes.
What this changed in practice
I now write the server rule first and the browser rule second, because writing them the other way round tempts you into treating the second one as optional. Closely related: defaults are a security decision.
Comments
Comments run on GitHub Discussions through giscus, so there is no database here and no account details for me to hold.
To switch it on: make the repo public, enable Discussions, install the giscus app, then set
giscus.repoinsrc/lib/site.ts. Until then this page makes no third-party request.