CodingIdeas.ai
← Browse All Ideas

Build Guide

How to Build a Login System

Building auth from scratch in 2026 is optional — but understanding what happens under the hood is not, because every security incident traces back to an auth mistake.

Intermediate1–3 days

Authentication is the first thing every SaaS needs. Build it yourself once to understand sessions, JWTs, OAuth flows, and password hashing — then use a managed service in production.

Data model

The core tables you'll need before writing any UI.

Userid, email, password_hash, email_verified, role (user/admin), created_at
Sessionid (random token), user_id, expires_at, created_at, ip, user_agent
PasswordResetTokentoken (random), user_id, expires_at (1 hour), used_at
OAuthAccountprovider (google/github), provider_user_id, user_id, access_token, refresh_token

Build order

The sequence that minimises rewrites — build in this order.

1

Password hashing and sign-up

Hash passwords with bcrypt (cost factor 12). Never store plain text. On sign-up: hash the password, insert the user, send a verification email with a signed token. Reject login until email is verified.

2

Session-based login

On login: verify email + bcrypt.compare(password, hash). If correct, generate a random session token, store it in the sessions table, and set it as an HttpOnly Secure cookie. Check this cookie on every protected request.

3

Middleware auth guard

Create a middleware function that reads the session cookie, looks up the session in the DB, checks it hasn't expired, and attaches the user to the request. Return 401 if missing or expired.

4

Password reset flow

POST /auth/forgot-password: find user by email, generate a random token, store in password_reset_tokens with 1-hour expiry, send email with reset link. POST /auth/reset-password: verify token not expired, hash new password, delete the token.

5

Google OAuth

Register an OAuth app in Google Cloud Console. Implement the OAuth2 flow: redirect to Google's auth URL → Google redirects back with a code → exchange code for tokens → fetch user profile → find or create your User row → create session.

6

Role-based access control

Add a role field to users (user / admin). In middleware, check role for admin-only routes. Create a withRole(role) higher-order function that wraps route handlers and rejects insufficient roles with 403.

7

Rate limiting

Limit /auth/login to 5 attempts per IP per 15 minutes using an in-memory counter (or Redis for multi-server). After 5 failures, return 429 Too Many Requests. Reset the counter on successful login.

Done when

Observable behaviors that confirm V1 is complete — verify each one before you ship.

  • Sign-up flow delivers a verification email within 60 seconds

  • Password reset works end-to-end: user gets link, clicks it, sets new password

  • Google OAuth signs user in and redirects to protected dashboard without errors

  • Seventh login attempt returns 429 (rate limit confirmed in DevTools)

  • Session cookie is HttpOnly and Secure (verified in DevTools → Application → Cookies)

First Run Requirement

Seed one test user (test@example.com / Test123!) via a seed script so the full auth flow can be verified immediately without going through sign-up manually.

Build it with AI — Architect Prompt

Paste this into Claude, Cursor, Windsurf, or any AI coding tool. It includes the full context of this guide — data model, build order, done conditions, and pitfalls — so the AI starts with everything it needs.

ClaudeCursorWindsurfCopilotGemini
architect-prompt.txt
<context>
App: Login System
Difficulty: Intermediate
Estimated build time: 1–3 days
Tech stack (intermediate): Supabase Auth + Next.js — open source, self-hostable, Postgres-native

Data model:
  User: id, email, password_hash, email_verified, role (user/admin), created_at
  Session: id (random token), user_id, expires_at, created_at, ip, user_agent
  PasswordResetToken: token (random), user_id, expires_at (1 hour), used_at
  OAuthAccount: provider (google/github), provider_user_id, user_id, access_token, refresh_token

FIRST RUN REQUIREMENT:
Seed one test user (test@example.com / Test123!) via a seed script so the full auth flow can be verified immediately without going through sign-up manually.

DONE WHEN — verify each before marking V1 complete:
  □ Sign-up flow delivers a verification email within 60 seconds
  □ Password reset works end-to-end: user gets link, clicks it, sets new password
  □ Google OAuth signs user in and redirects to protected dashboard without errors
  □ Seventh login attempt returns 429 (rate limit confirmed in DevTools)
  □ Session cookie is HttpOnly and Secure (verified in DevTools → Application → Cookies)

Recommended build order:
  1. Define API contract + schema + seed data (always first)
  2. Password hashing and sign-up — Hash passwords with bcrypt (cost factor 12). Never store plain text. On sign-up: hash the password, insert the user, send a verification email with a signed token. Reject login until email is verified.
  3. Session-based login — On login: verify email + bcrypt.compare(password, hash). If correct, generate a random session token, store it in the sessions table, and set it as an HttpOnly Secure cookie. Check this cookie on every protected request.
  4. Middleware auth guard — Create a middleware function that reads the session cookie, looks up the session in the DB, checks it hasn't expired, and attaches the user to the request. Return 401 if missing or expired.
  5. Password reset flow — POST /auth/forgot-password: find user by email, generate a random token, store in password_reset_tokens with 1-hour expiry, send email with reset link. POST /auth/reset-password: verify token not expired, hash new password, delete the token.
  6. Google OAuth — Register an OAuth app in Google Cloud Console. Implement the OAuth2 flow: redirect to Google's auth URL → Google redirects back with a code → exchange code for tokens → fetch user profile → find or create your User row → create session.
  7. Role-based access control — Add a role field to users (user / admin). In middleware, check role for admin-only routes. Create a withRole(role) higher-order function that wraps route handlers and rejects insufficient roles with 403.
  8. Rate limiting — Limit /auth/login to 5 attempts per IP per 15 minutes using an in-memory counter (or Redis for multi-server). After 5 failures, return 429 Too Many Requests. Reset the counter on successful login.
  9. End-to-end verification — walk every Done When condition above (always last)

Known pitfalls to avoid:
  - Using JWT for sessions without a revocation mechanism — if you store sessions as stateless JWTs, you can't log a user out on the server side (a stolen token stays valid until expiry). Use server-side sessions stored in a database.
  - Storing passwords with MD5 or SHA-1 — these are not password hashing algorithms. Use bcrypt, scrypt, or argon2. They're designed to be slow, which is exactly what you want for password hashing.
  - Exposing the session token in the URL — always use HttpOnly cookies, never URL params or localStorage. localStorage is accessible to JavaScript (and thus XSS attacks).
</context>

<role>
You are a Senior Full-Stack Engineer and product architect who has shipped production Login Systems before. You know exactly where developers get stuck and how to structure the project to avoid rewrites.
</role>

<task id="step-1-clarify">
Before writing any code or spec, ask me 3–5 clarifying questions that will meaningfully change the architecture. Focus on: scale expectations, auth requirements, platform (web / mobile / both), must-have vs nice-to-have features for the MVP, and any hard constraints (budget, deadline, existing infrastructure).

<example>
BAD: "What tech stack do you want to use?" — too broad, doesn't change architecture decisions.
GOOD: "Do you need real-time sync across devices, or is single-device with periodic refresh acceptable? This decides whether we use WebSockets or simple REST polling and significantly affects infrastructure complexity."
</example>

⚠️ Do NOT start planning or writing code until I answer. Present the questions, then stop and wait.
</task>

<task id="step-2-architect">
After I answer your questions, produce the following in order:

1. TECHNICAL SPECIFICATION
   - System architecture using → to show data and event flow
   - Core data model (refer to data model in <context>)
   - API contract — list ALL routes with request/response shapes BEFORE any implementation
     WHY: agreeing on contracts first prevents rewrites when frontend and backend shapes diverge
   - External APIs and integration points

2. MVP PLAN in three phases:

   SETUP   ✦ Step 1 (always): API contract + schema migration + seed data
     Done when: schema is migrated, seed script runs, app starts with demo data, zero manual setup.

   CORE FEATURES — build in this exact order:
   2. Password hashing and sign-up
   3. Session-based login
   4. Middleware auth guard
   5. Password reset flow
   6. Google OAuth
   7. Role-based access control
   8. Rate limiting

   DONE WHEN — one observable condition per feature:
   □ Sign-up flow delivers a verification email within 60 seconds
   □ Password reset works end-to-end: user gets link, clicks it, sets new password
   □ Google OAuth signs user in and redirects to protected dashboard without errors
   □ Seventh login attempt returns 429 (rate limit confirmed in DevTools)
   □ Session cookie is HttpOnly and Secure (verified in DevTools → Application → Cookies)

   STRETCH GOALS — post-launch additions that are explicitly out of V1 scope.

3. BLOCKER ANALYSIS
   Flag: API rate limits, auth complexity, scope risks, cold-start problems, top 2–3 failure modes.

   FIRST RUN REQUIREMENT: Seed one test user (test@example.com / Test123!) via a seed script so the full auth flow can be verified immediately without going through sign-up manually.
   ✓ Done when: app launches with seed data and the full user journey works without any manual setup.

<self_check>
Before presenting your output, verify:
□ Every answer I gave to clarifying questions is reflected in the spec
□ Step 1 is ALWAYS: API contract + schema + seed — never UI first
□ Done When criteria are observable user behaviors, not internal states
□ The plan is realistic for one person to ship within 1–3 days
□ All known pitfalls from <context> are addressed in the spec
</self_check>
</task>

<task id="step-2.5-agents-md">
Generate an AGENTS.md file at the project root. This file is read automatically by Claude Code, Cursor, Windsurf, and all major AI coding tools at the start of every session.

Include:
- Project overview (2–3 sentences)
- Tech stack with exact version numbers
- Folder structure with one-line descriptions per directory
- Key architectural decisions and the WHY behind each
- Coding conventions: camelCase components, kebab-case files, max 200 lines per file, one concern per file
- Available commands: dev, build, test, lint, db:migrate, db:seed
- MVP scope boundaries — features explicitly out of V1

Note in the file: "Cursor users: symlink .cursorrules → AGENTS.md. Claude Code users: symlink CLAUDE.md → AGENTS.md."
</task>

<task id="step-3-implement">
Implement the full project in the exact order from step 2. For each step:
- Write complete code (no placeholders or TODOs)
- Confirm the step works before moving to the next

<constraints>
- Step 1 is ALWAYS: define all API routes + TypeScript request/response interfaces + run schema migration
  WHY: agreeing on contracts before writing a single component prevents shape mismatches that require rewrites
- Final step is ALWAYS: start the app and walk every Done When condition from <context> end-to-end
  WHY: a spec that passes unit tests but breaks the core user journey is not done
- Max 200 lines per file. WHY: every file must fit in one AI context window for complete reasoning
- One concern per file. WHY: mixing auth logic into API routes makes it impossible to reuse — extract to lib/auth.ts
- TypeScript strict mode, no `any` types. WHY: catches data shape mismatches at compile time, not in production
- All database queries in server components or API routes only. WHY: keeps credentials server-side
- All environment variables documented in .env.example. WHY: next developer sets up in under 5 minutes
</constraints>
</task>

How to use this prompt

  1. 1.Copy the prompt above
  2. 2.Open Claude, Cursor (Cmd+L), or Windsurf
  3. 3.Paste the prompt and send — the AI will ask 3–5 clarifying questions
  4. 4.Answer the questions, then it generates your full project spec
  5. 5.Continue in the same session to implement step by step

Common mistakes

What trips up most developers building this for the first time.

⚠️

Using JWT for sessions without a revocation mechanism — if you store sessions as stateless JWTs, you can't log a user out on the server side (a stolen token stays valid until expiry). Use server-side sessions stored in a database.

⚠️

Storing passwords with MD5 or SHA-1 — these are not password hashing algorithms. Use bcrypt, scrypt, or argon2. They're designed to be slow, which is exactly what you want for password hashing.

⚠️

Exposing the session token in the URL — always use HttpOnly cookies, never URL params or localStorage. localStorage is accessible to JavaScript (and thus XSS attacks).

Recommended tech stack

Pick the level that matches your experience.

Beginner

Clerk — 10 lines of code, full auth system with UI. Use this in production.

Intermediate

Supabase Auth + Next.js — open source, self-hostable, Postgres-native

Advanced

Auth.js (NextAuth) + Prisma + your own DB — full control, more responsibility

Take it further — related ideas

Each comes with revenue math, a full build guide, and a prompt to paste into Claude or Cursor.