Who are you?
Welcome to the course. Before we touch any code, we need to talk about a problem that sits at the heart of almost every web app you will ever build: how does a server know who is talking to it? You have probably used login forms a thousand times. You type an email, a password, click a button, and somehow the site remembers you for the rest of the day. That whole process is what we are going to build from the ground up over the next few lessons. But first, let’s slow down and look at what the server is actually dealing with.
The problem nobody warns you about
Imagine you are running a small office. Someone walks up to the front desk and says, “Hi, I need to see the financial records.” Before you hand anything over, you need to answer two questions, in order:
- Who is this person? Are they really who they say they are?
- Are they allowed to see this? Even if they are who they claim, do they have permission?
These feel like the same question, but they are not. They are actually two completely separate problems, and real apps handle them in two separate steps. The first question is called authentication. The second is called authorization. You will hear these words thrown around constantly, and people mix them up all the time, so let’s nail them down now.
Authentication is about identity. It is the process of proving you are who you say you are.
Authorization is about access. It is deciding what a proven identity is allowed to do.
You always authenticate first, then authorize. It makes no sense to check permissions for someone whose identity you have not even verified yet. If you cannot prove who someone is, you cannot reason about what they are allowed to do.
You already deal with this every day
You might not realize it, but you deal with authentication constantly in normal life.
When you unlock your phone with your fingerprint, your phone is authenticating you. The fingerprint proves you are the phone’s owner, not just someone who picked it up off a table. When you log in to your email with a password, the email service is authenticating you. The password is something only you should know, so typing it correctly proves who you are. When you show your passport at an airport, the border agent is authenticating you. The passport is a trusted document that links your photo to your name.
Notice the pattern. In every case, you present something (a fingerprint, a password, a document) and the other party checks it against what they expect. If it matches, you are authenticated.
That is literally what a web server has to do too. Except the server cannot see your face, cannot read your fingerprint, and cannot hold your passport. All it gets is some bytes over a wire.
What a server actually sees
Here is the part that surprises most people when they first learn it. A web server is like that front desk, but it is a very confused one. Requests arrive constantly from all over the world, and the server has absolutely no built-in way to tell who is sending them.
When your browser sends a request, the server sees something like this:
GET /account HTTP/1.1
Host: example.com That is it. That is literally everything the server has to work with. There is a method (GET), a URL (/account), and a host. Maybe some headers and a body. Nowhere in the HTTP specification is there a field that says “this request is from Alice.” Every request is anonymous by default.
So what do we do when someone hits /account? Whose account are we even supposed to show? The server has no idea. If we want the server to know, we have to build that ability ourselves.
What if we just tell the server who we are?
This is where most people, on their first try, reach for the obvious solution:
GET /account HTTP/1.1
Host: example.com
X-User-Id: 42 “Just add a header. Problem solved.” What do you think happens if we try this?
It is a disaster. And not in some subtle, edge-case way. In a “this can be broken in five seconds” way. Here is why.
Anyone can send that header. Literally anyone. I can open a terminal right now, type curl -H "X-User-Id: 1" https://yoursite.com/account, and claim to be user 1. Your server has no way to know if I am actually user 1 or if I am just some random person who decided to type that number. If user 1 happens to be an admin, I just became an admin. Congratulations, I own your app.
This leads us to a rule that you should tattoo on your brain because it never stops being relevant:
Never trust data from the client.
The client is not under your control. Anyone can craft an HTTP request with any headers, any body, any cookies, any values. It might be a browser. It might be a script. It might be an attacker. The server has no idea, and it cannot tell the difference. So anything the server relies on for security has to be verified, not just accepted.
Authentication is the mechanism for that verification. Instead of taking the client’s word for who they are, the server checks proof. That proof could be a password, a signed token, or an ID that the server itself issued. The details will come later. The point for now is that the server does the checking. The client does not get to declare its own identity.
A concrete picture
Let’s ground this with an example, since the abstract version can feel slippery. Picture a blogging platform with three types of users: readers, authors, and admins.
Authentication answers “who is making this request?” It does not care what they want to do. It just establishes identity.
Authorization answers “is this authenticated user allowed to do this specific thing?” It depends on the identity that authentication already established.
| Action | Authentication | Authorization |
|---|---|---|
| View a public post | Not needed | Not needed |
| Edit your own post | Must prove you are you | Must be the post’s author |
| Delete any post | Must prove you are you | Must be an admin |
| View your profile | Must prove you are you | Allowed (it is your own data) |
| View someone else’s private data | Must prove you are you | Not allowed (not your data) |
Some actions need neither (public pages that anyone can see). Some need authentication but no special authorization (viewing your own profile is fine if you are logged in). Some need both (deleting any post requires both logging in and being an admin).
Real apps are full of these little decision trees, and you will write them constantly. For now, the important thing is that you can see why these are two separate problems.
What we are going to build
Over the next lessons, you are going to build authentication from scratch. Not by installing some giant library that does everything for you and hides the mechanics. You are going to build every piece yourself so you understand what is actually happening under the hood:
- Passwords: how to safely store them and how to check them later
- Sessions: how to remember who someone is across many requests
- Cookies: how browsers carry identity back and forth automatically
- Tokens (JWTs): a different approach where the identity rides inside the request itself
- Authorization: how to check permissions once someone is authenticated
You will write all of this as plain TypeScript functions using a small HTTP framework called Hectoday HTTP. No magic. No hidden middleware that runs invisibly. Just functions that take a request and return either a verified identity or an error. You will be able to read every handler top to bottom and see exactly what is happening.
In the next lesson, we dig into why HTTP itself makes this hard. There is a reason authentication is such a common topic and a common source of bugs, and it starts with a single word: stateless.
A user logs in successfully, then tries to delete another user's account. The server rejects the request. What rejected it?
Why can't the server trust a custom header like X-User-Id sent by the client?