The principles of client-server communication

Are you new to backend? Do you want to learn how to authenticate users properly? This post will not go over any of the specfics, but it will describe the general principles of client-server communication you need to understand to successfully write a backend and connect it to your web users. These principles do not just cover the web — they cover any form of client-server communication — but that will be the main focus here.

Roughly speaking, there are two things you need to know:

Either way it boils down to the same thing: verify all your requests and make sure you make no assumptions about them when handling them server-side. In particular, this means do not rely on any client-side form controls: Inspect Element or just curl can trivially disable them. Client-side checks exist for convenience, not security.

Authenticate for each request

The client will make separate requests for each distinct action it wants to take with the server. For instance, if I wanted to write a new topic in a forum that would be a POST request (POST requests are generally used for write operations to the server’s database). If I wanted to then reply to that same topic, it would be a separate POST request. And if I wanted to read said topic and reply later, that would be a GET request (generally used for read-only operations). Each action the client takes is one request. (This applies for loading a page too: when you go to dennisc.net, you’re making a POST request to the route /.)

To protect against malicious requests the client has to prove within each request, via means of a cookie or a token, that they are who they say they are. This is authentication.

There are two common ways to authenticate on the web that we will cover: username/password and session tokens.. To simplify, a website usually works as thus:

The browser automatically sends the session token to verify its identity by storing it in a cookie, which it then automatically sends on every request. This is what cookies are: just information the browser sends on your behalf for every request to a website.

Anyone can make an arbitrary request

Whenever people first try to write a full-stack web “app”1, they inevitably fall into the trap of thining that only officially approved clients (i.e. only the frontend of your website, and not any external actors malicious or otherwise) will send requests to your server. This leads to embarrassing things like “authenticating” requests based on username rather than something verifiable like a session ID.2

Personally, downloading Postman has been really informative. Not only is it possible, it is also easy for any user to send whatever they want to your endpoints. cURL should be similarly enlightening.

In any case, you can either only allow your website to act as a client (this is done for forms using a CRSF token, but you should not solely rely on this) or accept that clients can send whatever they want. This also means that any clientside checks in formdata must also be done serverside.

Summary

The server has no memory of clients, so they must send identifying information on each request. AAnyone can act as a a client and they can send anything, and you must verify the validity of each request server-side.

Basically, assume every request is made with malicious intent until they prove otherwise.3