Building Apps with AILesson 53 of 87

Contents

Building Apps with AI

Lesson 53 of 87. Sign in to keep your place.

Module 6 · The parts that persist: data, accounts and files

Sessions, cookies, and who the server thinks you are

8 min read

Where the route got the user idOwn id sentAnother id typed inFrom the session cookieYour rowscorrectYour rowsthe body is ignoredFrom the request bodyYour rowscorrectTheir rowsthe holeThree of these four cells behave identically in every test, because your own front end always sendsthe real id. The fourth is what happens when somebody edits it in the developer tools.
Three of these four cells behave identically in every test, because your own front end always sends the real id. The fourth is what happens when somebody edits it in the developer tools.

Figure

HTTP has no memory

Every request arrives on its own. The server that handled your sign-in a minute ago does not remember you; on a serverless host it may not even be the same process. So something in each request has to say who is making it, and the browser’s mechanism for that is the cookie.

What a session cookie is

After sign-in, the server generates a long random string, stores it in a sessions table beside your user id and an expiry, and tells the browser to keep it:

Text
Set-Cookie: session=k8Fz...3Qa; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=2592000

From then on the browser attaches that cookie to every request to your domain, without any code on your side asking. The server reads it, looks up the row, and now knows who you are. Signing out deletes the row; the cookie in the browser becomes a string that matches nothing.

The three flags matter. HttpOnly means JavaScript in the page cannot read the cookie, so a script injected by an attacker cannot steal it. Secure means it travels only over HTTPS. SameSite=Lax means other sites cannot make your browser send it with their requests, which closes a whole class of attack in one word. The auth library sets all three; check that they are there in the browser’s devtools, under Application, Cookies, because a model hand-rolling a session will forget at least one.

Where identity comes from

This is the sentence the rest of the course leans on. The server learns who you are by looking up the cookie. Nothing else in the request is evidence of identity.

Generated code breaks this in a specific, common way. A route accepts a JSON body containing userId and uses it, because the front-end code that calls the route helpfully includes it. It works in every test, because the front end sends the real id. It is also an invitation: anyone can open the developer tools, change the id, and read someone else’s rows. The server had no way to know, because it never asked the one source it could trust.

The fix is a pattern, applied to every route:

TypeScript
const session = await getSession(request);      // reads the cookie, looks it up
if (!session) return new Response("Unauthorized", { status: 401 });
const rows = await db.select().from(bookings).where(eq(bookings.userId, session.userId));

The id comes from the session, the body is used only for what the body is for, and a request with no valid cookie is turned away before it reaches the database.

Two ways to store a session

The database session above is one row per login. It can be revoked instantly (delete the row) and it costs one lookup per request.

The alternative is a signed token, often a JWT, where the server puts the user id and expiry into the cookie itself and signs it, so it can verify the cookie without a lookup. Faster, and harder to revoke: the token is valid until it expires, wherever it is, and a “log out everywhere” button has to be built with extra machinery. For a small app the database session is simpler and its cost is invisible. If a library gives you the token kind, keep the expiry short.

Expiry and the second device

Sessions should end. Thirty days for a consumer app, hours for anything that handles money, and the auth library has a setting. A user signed in on a phone and a laptop has two session rows, and signing out on one should not sign out the other unless they ask for that. Test both, because the version where signing out anywhere signs out everywhere is the one that gets written by accident.

Reading a session in a server component

In frameworks that render on the server, the same lookup happens before the page is built. A page that shows “Welcome, Priya” fetched the session on the server and rendered with it. If the same page works when signed out and shows “Welcome, undefined”, the check was never there, only the greeting.

Your users table, not theirs

Whichever library or provider authenticates, keep a users table of your own, and have every other table reference it. The library’s user record can be swapped, merged or migrated; your user_id foreign keys stay put. This is the shape that lets an app move from one provider to another with one migration instead of a rewrite, and it is a decision that is nearly free on day one and expensive on day three hundred.

Every lesson here is open. Your progress is recorded once you pass the Building with AI exam.

Check your understanding

A route reads userId from the JSON body of the request and returns that user’s bookings. The developer tests it while logged in and it returns the right rows. What is wrong?

Pick the one you would defend. Your answer stays private.

Next lesson · 8 minFiles and uploads, and where they must not go

Stuck on something?Ask Beatriz, your AI tutor

How to cite: Addaly (2026) ‘Sessions, cookies, and who the server thinks you are’, in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/sessions-cookies-and-who-you-are (Accessed: 13 October 2026).