Intermediate · Authentication and files end to end · topic 21 of 34 · 30 min
Signup and login end to end
Explanation
Two new words: authentication and session
Authentication means proving who you are. In the Notes app you prove it with an email and a password. After that, the server must remember you, so you do not type the password on every click.
A session is that memory. The server makes a long random id (the session id), saves "this id belongs to user 7", and gives the id to the browser inside a cookie. A cookie is a small piece of text the browser stores and sends back to the same website automatically.
Signup, step by step
- The user fills a form: name, email and a password of at least 8 characters.
- The browser checks the fields for quick feedback, then sends POST /api/auth/signup with JSON.
- The server checks the same rules again (the browser checks can be skipped by anyone).
- The server hashes the password with Argon2id or bcrypt and saves name, email and password_hash in the users table.
- If the email already exists, the server answers 409 Conflict. Otherwise it answers 201 Created (and often logs the user in straight away).
import argon2 from 'argon2';
app.post('/api/auth/signup', async (req, res) => {
const name = String(req.body.name || '').trim();
const email = String(req.body.email || '').trim().toLowerCase();
const password = String(req.body.password || '');
if (!name || password.length < 8) {
return res.status(400).json({ error: 'Name and a password of 8+ characters are required' });
}
const passwordHash = await argon2.hash(password); // Argon2id by default
const existing = await db.findUserByEmail(email);
if (existing) return res.status(409).json({ error: 'Email already registered' });
const user = await db.createUser({ name, email, passwordHash });
return res.status(201).json({ id: user.id, name: user.name });
});Login, the cookie and GET /api/me
| Step | Browser | Server |
|---|---|---|
| 1. Login | POST /api/auth/login with email and password | Finds the user, checks the password against the hash |
| 2. Session | Receives Set-Cookie: sid=... | Saves a session row: random id, user id, expiry |
| 3. Every request | Sends Cookie: sid=... automatically | Looks up the session to find the user id |
| 4. Who am I? | GET /api/me when the app loads | Returns { id, name } or 401 if no valid session |
| 5. Logout | POST /api/auth/logout | Deletes the session row and clears the cookie |
Set-Cookie: sid=Jx8f...long-random-id; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=604800const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (res.status === 401) showError('Wrong email or password');
else if (res.ok) location.href = '/notes';Session or JWT? A JWT (JSON Web Token) is a signed token that holds the user id itself, so the server does not need a session table. But a JWT is hard to cancel before it expires. For a normal web app like Notes, a session cookie is simpler and safer. JWTs fit better when many separate services or mobile apps must check the same login.
Build it yourself or use a library?
| Option | What it is | Good when |
|---|---|---|
| Auth.js or Better Auth | Open-source libraries you add to your own app (popular with Next.js) | You want control and your users in your own database |
| Clerk | A paid hosted service with ready login screens | You want to ship fast and accept a monthly bill as you grow |
| Supabase Auth or Firebase Auth | Login that comes with a hosted backend platform | You already use Supabase or Firebase |
| Build it yourself | Your own hashing, sessions, emails | Learning (like this course) or very special needs |
Email verification and password reset
- Both use a one-time token: a long random string, saved (better: saved as a hash) with the user id and a short expiry, such as 30 minutes.
- The server emails a link like /reset?token=..., the user clicks it, and the server checks the token is real, unused and not expired.
- After use, delete the token so the link cannot work twice. After a password reset, also end the old sessions.
- On "forgot password", always reply "If this email exists, we sent a link", so nobody can test which emails are registered.
Watch the lesson
Signup and login end to end · 1/7
Today we add login to the Notes app, from the form all the way to the database.
Prefer a video by a teacher? Find free videos on “session cookie authentication login flow explained” on YouTube ↗
In real life: Logging in to a college results site
A student signs up on a college portal with an email and password. The server stores only an Argon2id hash. On results day she logs in: the server checks the hash, creates a session and sends back an HttpOnly cookie. Each time she opens "My marks", the browser sends the cookie and the server looks up which student she is, so she sees only her own marks. When she clicks "Logout" on a shared computer in the library, the server deletes the session, and the old cookie is useless even if someone copies it.
Practice (0/3 done)
Write validateSignup(input) for the signup form. input has name, email and password. Return an array of the field names that are wrong, in the order name, email, password. Rules: name is not empty after trimming; email (trimmed) has text, then "@", then text, then ".", then text, with no spaces; password has at least 8 characters. Return [] when everything is fine.
Build an in-memory auth service: createAuth({ hash, verify, newSessionId }) returns { signup, login, me, logout }. The hash and verify functions are passed in (tests use a fake; real apps use Argon2id or bcrypt). signup(email, password): lowercase and trim the email; password shorter than 8 -> { status: 400, error: "Password too short" }; email already used -> { status: 409, error: "Email already registered" }; else save { id, email, passwordHash: hash(password) } with id = 1, 2, 3... and return { status: 201, userId: id }. login(email, password): unknown email or verify(password, passwordHash) false -> { status: 401, error: "Wrong email or password" }; else create a session with newSessionId() and return { status: 200, sessionId }. me(sessionId): valid -> { status: 200, user: { id, email } }; else { status: 401, error: "Not logged in" }. logout(sessionId): delete the session and return { status: 204 }.
Write the "forgot password" flow for the Notes app as numbered steps: what the user does, what the server stores, what the email contains, and what the server checks when the link is clicked.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.