Guided journey

Add Accounts and Login to Your App

A project-based journey for AI builders: choose an auth method, hash passwords, add sessions, and prove one user cannot read another user's data.

You walk away with

Working signup and login, sessions that end when they should, and per-user data that another account cannot reach by changing an ID.

Assume you have

An app with at least one feature and some storage — the app from Build and Ship Your First Web App, or any project where users create records.

Stack used

Server-rendered sessions with a hashed password and a managed auth provider as the upgrade path. Node.js or Python.

The project

You are adding accounts to an app that already works. The worked example is the link saver from the previous journey: each user gets their own private list. The security question is not “can someone log in?” — it is “can logged-out person A read person B’s links?”

By the end you will have: signup, login, logout, session expiry, and a two-account test that proves data isolation. That last test is the whole point of the journey.

Before you start

One warning before you prompt: auth is where “it works” and “it is safe” diverge most. A login screen that rejects the wrong password looks identical whether or not the session is forgeable. That is why every milestone here ends in a test, not a screenshot.

Milestone — Choose an authentication method on purpose

Build: a one-line decision about how users prove who they are.

Authentication methods are not interchangeable, and the wrong choice is expensive to undo later. Pick one and write down why.

Do it

  1. Answer the three questions in How to Choose an Authentication Method for Your App: who are the users, what are the clients, and how much account recovery do you want to own?
  2. For a small web app, the usual answer is a managed provider or plain email-and-password with server sessions.
  3. Write the decision and its trade-off into NOTES.md.

Done when you can say out loud which method you chose and which two you rejected. If you are unsure what the options even are, skim What Are the Different Types of Authentication? first.

Build: a users table and a user_id on every record that belongs to someone.

Authorization is a data-model problem before it is a code problem. If records do not carry their owner, no amount of guard clauses will save you.

Prompt your agent

Add authentication to the app per SPEC.md, using server sessions.

Data model:
- users: id, email (unique), password_hash, created_at.
- links: add a non-nullable user_id referencing users.id,
  with ON DELETE CASCADE.
- Write the migration. Do not add login routes yet.

Explain how existing links without an owner will be handled.

Done when the schema has a real foreign key, and you have decided what happens to the pre-auth records. How to Design Authorization for Your App covers the ownership, role, and tenant patterns you will need next.

Milestone — Store passwords correctly (or don’t store them at all)

Build: signup that hashes passwords with Argon2, bcrypt, or scrypt.

If you store passwords reversibly, one database leak becomes every user’s problem on every other site. This milestone has a correct answer and an assistant that sometimes gets it wrong.

Do it

  1. Ask your agent to implement signup using a slow hashing algorithm — Argon2id, bcrypt, or scrypt.
  2. Explicitly forbid encryption, base64, or a fast hash like MD5 or SHA-256.
  3. Verify in the database: the password_hash column starts with a hash prefix such as $argon2id$ or $2b$, never the plaintext.

Done when you can look at one row and see a hash, not a password. How to Store Passwords Correctly (Hashing vs Encryption) has the code examples and the exact mistake to watch for.

Milestone — Add login, sessions, and logout

Build: a login route that sets an httpOnly cookie, and a logout that clears it.

The session is the state that says “this request is this user.” Get its three properties right — httpOnly, Secure, SameSite — and most client-side attacks stop mattering.

Prompt your agent

Implement login and logout using sessions.

- On success, set a cookie with HttpOnly, Secure, SameSite=Lax,
  and a max-age of seven days.
- Regenerate the session id on login to prevent fixation.
- Logout must destroy the server-side session, not just clear
  the cookie.
- On failure, return one generic message: "Invalid email or
  password." Never reveal which field was wrong.

Show me the files changed and the cookie flags you set.

Done when login works, the session cookie is visible in developer tools with HttpOnly and Secure, and a request with the cookie deleted is treated as logged out. If you chose tokens instead, read JWT Security: Common Mistakes That Get Tokens Stolen before shipping.

Milestone — Protect the routes and enforce ownership

Build: a guard on every private page, and an owner filter on every query.

This is the milestone assistants skip. Hiding the edit button is not access control; the endpoint must refuse the request on its own.

Do it

  1. Ask your agent to list every route and mark it public or private, then gate the private ones.
  2. For each private query, require a filter on the current user’s id — the ownership must be in the query, not in an if after it.
  3. Deny by default: an unmarked new route should be private until proven otherwise.

Done when requesting another user’s record by id returns 404 or 403, not the record. That check is broken access control, the most common defect in generated CRUD apps.

Milestone — Prove isolation with two accounts

Build: a repeatable test that one user cannot see another’s data.

Do not trust that the previous milestone worked. Make it observable, and make it something you can re-run on every future change.

Do it

  1. Create two accounts, A and B, in two separate browser windows.
  2. As B, create a record. Note its id in the URL or API response.
  3. Log in as A and request that id directly — by editing the URL or sending the request.
  4. Repeat the swap for an update and a delete, not just a read. Generated code often guards reads and forgets writes.

Done when every cross-account attempt fails and you have saved the steps into NOTES.md. How to Test Your App for Broken Access Control is the full version of this test.

Milestone — Tighten the edges

Build: rate limiting, security headers, and a logout that actually ends the session.

Auth features are only as good as their weakest edge. Four small additions close most of the remaining gaps.

Do it

  1. Add rate limiting to login and signup — see How to Add Rate Limiting to an API.
  2. Add the standard security headers, including HSTS and a content security policy.
  3. Confirm CSRF protection on state-changing form posts — What Is CSRF?.
  4. Move the session secret and database URL into environment variables, never the repo. Follow How to Manage Secrets and Environment Variables Properly.

Done when six rapid wrong-password attempts get throttled, the security headers appear in a response, and a repo-wide search for your secrets finds nothing. The 12 Authentication Mistakes That Show Up in AI-Generated Apps is the checklist to run against the finished feature.

Ship checklist

  • You chose an auth method deliberately and can justify it.
  • Passwords are hashed with Argon2, bcrypt, or scrypt — never encrypted.
  • Session cookies are HttpOnly, Secure, and SameSite.
  • Every private route is gated, and ownership is enforced in the query.
  • Login and signup are rate limited.
  • The two-account swap test fails for reads, updates, and deletes.
  • No secrets, session keys, or .env files are committed.

Where this journey goes wrong

  • Guarding the page, not the endpoint. Anyone can call the API directly; the server must refuse.
  • Trusting a hidden user id. A user_id sent from the client is attacker-controlled input, not identity.
  • Storing passwords reversibly. Encryption is not hashing, and base64 is neither.
  • Sessions that never expire. A stolen session token should stop working.
  • Email enumeration. Different responses for “user exists” and “wrong password” hand attackers a user list.

FAQ

Should I use sessions or JWTs?

For a server-rendered app, sessions in an httpOnly cookie are the safer default: instant revocation, no signing secret to misconfigure. Reach for JWTs when another service or a mobile client must verify identity without a session store — and then apply the known fixes in JWT Security: Common Mistakes That Get Tokens Stolen.

Is a managed auth provider cheating?

No — it is usually the right call. A provider outsources password reset, MFA, and breach response, which are expensive to own. You still have to do the ownership check yourself, because no provider knows which of your records belong to whom.

Share: