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.
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.
Build order
The sequence that minimises rewrites — build in this order.
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.
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.
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.
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.
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.
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.
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.
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, and pitfalls — so the AI starts with everything it needs.
<context> App: Login System Difficulty: Intermediate Estimated build time: 1–3 days 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 Recommended build 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. 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). Tech stack (intermediate): Supabase Auth + Next.js — open source, self-hostable, Postgres-native </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> </task> <task id="step-2-architect"> After I answer your questions, generate a complete project specification including: 1. Final tech stack with version numbers 2. Full file and folder structure 3. Complete database schema — every table, column, type, index, and foreign key 4. All API routes with request/response shapes and auth requirements 5. Component tree with TypeScript props interfaces 6. Auth and authorisation flow (who can do what) 7. Step-by-step implementation plan in the exact build order from <context> above 8. All environment variables with descriptions <self_check> Before presenting the spec, verify: □ Every answer I gave in step 1 is reflected in the spec □ The database schema matches the data model in <context> □ The implementation plan follows the build order from <context> □ No circular dependencies in the component tree □ Auth is wired up before any protected routes are built □ 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, Sourcegraph Cody, and all major AI coding tools at the start of every session. Include: - Project overview (2–3 sentences) - Tech stack with version numbers - Folder structure with one-line descriptions - Key architectural decisions and why they were made - Coding conventions: naming (camelCase components, kebab-case files), max 200 lines per file, one concern per file - Available commands: dev, build, test, lint, db:migrate, db:seed Note in the file: "Cursor users can symlink .cursorrules → AGENTS.md. Claude Code users can symlink CLAUDE.md → AGENTS.md." </task> <task id="step-3-implement"> Implement the full project following the spec from step 2, in the exact order defined. For each step: - Write the complete code (no placeholders or TODOs) - Confirm the step is working before moving to the next - Note any deviations from the spec with an explanation <constraints> - Max 200 lines per file. WHY: every file must fit in one AI context window for easy review and editing. - 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 at 2am. - All database queries in server components or API routes only. WHY: keeps credentials server-side, prevents accidental client-side exposure. - All environment variables documented in .env.example. WHY: the next developer (or your future self) should be able to set up the project in under 5 minutes. - Comment every non-obvious decision. WHY: AI tools read your comments to understand intent — without them, the next edit will break the pattern you established. </constraints> </task>
How to use this prompt
- 1.Copy the prompt above
- 2.Open Claude, Cursor (Cmd+L), or Windsurf
- 3.Paste the prompt and send — the AI will ask 3–5 clarifying questions
- 4.Answer the questions, then it generates your full project spec
- 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.
Clerk — 10 lines of code, full auth system with UI. Use this in production.
Supabase Auth + Next.js — open source, self-hostable, Postgres-native
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.
9 ideas in the archive
ContractPulse - Vendor Renewal and Health Monitor for Bootstrapped SaaS Teams
Small SaaS teams routinely lose money on zombie vendor contracts because no one owns the renewal calendar. ContractPulse ingests your vendor invoices and emails, surfaces upcoming renewals, flags price drift, and sends Slack alerts 30 days out. It's the CFO dashboard you couldn't afford to hire.
IntentShift - NLP Exit Intent Classifier for SaaS Cancellation Flows
Most SaaS cancellation surveys are graveyards of checkbox data nobody reads. IntentShift uses NLP to classify open-text cancellation reasons into actionable churn drivers with confidence scores, then triggers the right retention playbook automatically.
WaitlistWarm - Pre-Launch Email Sequence Builder That Converts Waitlist Signups Into Day-One Paying Customers
You build a waitlist landing page, collect 400 emails, launch, and 3 people buy. WaitlistWarm builds and sends a 6-email pre-launch nurture sequence that educates, qualifies, and pre-sells your waitlist so that by launch day they are already reaching for their credit card. No Mailchimp template hell, no copywriting degree required.
ClaimParse - NLP Entity Extractor That Turns Dense Insurance Policy PDFs Into Structured Data
Insurance policy PDFs are written by lawyers for lawyers, but indie SaaS founders and insurtech startups need structured data from them right now. ClaimParse is a fine-tuned NER pipeline that extracts coverage limits, exclusions, deductibles, and effective dates from any uploaded insurance document and returns clean JSON. No more manual copy-paste into spreadsheets.
ShiftFill - Self-Service Shift Swap Marketplace for Hourly Retail and Hospitality Teams
Shift managers at retail and restaurant chains spend 2 hours every Sunday texting staff to fill open shifts because their scheduling software has no self-service swap layer. ShiftFill is a mobile-first marketplace where employees post and claim open shifts in real time with manager approval in one tap.
EORTrack - Remote Job Board for EOR-Sponsored Global Hiring
Remote job seekers waste hours crawling LinkedIn for companies that actually hire internationally via Employer of Record providers. EORTrack scrapes and aggregates EOR-friendly job listings with country eligibility tags, timezone filters, and EOR provider badges so you know before you apply.
BookSlot - Freelancer-First Scheduling With Built-In Payments and Session Types
Calendly was built for sales teams, not solopreneurs charging $200/hour for consulting sessions. BookSlot is a scheduling tool where paid sessions, custom meeting types, and instant rescheduling are table stakes — not $20/month add-ons.
IdeaScope - Instant Startup Idea Scorecard Without the Spreadsheet Hell
Founders waste hours manually Googling competitors, estimating TAMs, and arguing with their notes app about whether an idea is worth building. IdeaScope takes your raw idea and returns a scored validation card in 90 seconds: TAM estimate, competition density, suggested MVP stack, and a go/no-go signal. Stop journaling, start shipping.
DriftWatch - RAG-Powered Knowledge Base Staleness Detector for SaaS Docs Teams
Your help center says the button is blue. It turned orange in the last deploy. DriftWatch runs a nightly RAG agent that compares your live product UI screenshots against your documentation, surfaces every stale paragraph, and emails a diff report to your team.