DEV Community

Cover image for I Rebuilt My Auth System From Scratch — Here's the Before vs After That Changed Everything
VIKAS
VIKAS

Posted on

I Rebuilt My Auth System From Scratch — Here's the Before vs After That Changed Everything

GitHub “Finish-Up-A-Thon” Challenge Submission

This is a submission for the GitHub Finish-Up-A-Thon Challenge


Every developer has that project. The one you abandoned mid-way, not because the idea was bad, but because life moved faster than your git commits.

For me, it was my authentication system — a half-baked Express app with manual JWT logic, no password reset, no CSRF protection, and raw MongoDB errors leaking straight to the browser. It worked. Barely. I knew it was a security liability every time I pushed it, but it sat untouched in my GitHub graveyard for months.

The GitHub Finish-Up-A-Thon gave me the push I needed to stop ignoring it.

This is the story of how SecureAuthX went from "it probably works" to production-grade.


What I Built

SecureAuthX is a production-ready authentication boilerplate built on the full MERN stack (MongoDB, Express 5, React 18, Node.js), deployed on Vercel Serverless Functions.

The core idea: authentication is one of those things every web app needs, but most developers get wrong — especially under pressure. SecureAuthX is the auth layer I wish I had when I started building projects.

What it handles out of the box:

  • Email/Password login with bcrypt hashing and HTTP-only cookie sessions
  • Magic Links — passwordless authentication via secure one-time email tokens
  • Email verification with seamless auto-login on success (no extra redirect step)
  • Forgot Password + Reset flow with immediate auto-login after reset
  • Rate limiting at two layers: global (100 req/15min) and auth-specific (5 req/15min)
  • CSRF protection via mathematically verified Origin/Referer header checks
  • Input validation strictly on the backend via express-validator
  • Anti-leak error handling — no raw DB errors ever reach the client

The frontend is React 18 with Tailwind CSS, featuring floating labels, browser autofill overlap prevention, and micro-animations. Clean, responsive, and actually pleasant to use.

But here's the part that genuinely changed how I think about auth architecture:

Powered by @custom-auth/core

Instead of reimplementing JWT logic, session management, and token pipelines from scratch (again), I integrated the @custom-auth/core engine as the auth backbone. This is a modular npm library ecosystem built specifically for Node.js backends, and it changed the entire architecture of how sessions and tokens are handled in this project.

The stack uses:

  • @custom-auth/core — core auth engine on the backend (session creation, token verification, secure cookie issuance)
  • @custom-auth/mongoose — Mongoose adapter, handles user schema and token storage without any boilerplate
  • @custom-auth/react — React adapter, exposes hooks like useSession() and useSignIn() that replace an entire Redux Toolkit setup
  • @custom-auth/adapter-nodemailer — plugs directly into the magic link and email verification pipeline

The useSession() hook alone replaced what used to be a full Redux store with actions, reducers, thunks, and selectors. One hook. That's it.

// Before: Redux Toolkit + 4 files + thunk middleware
// After:
const { user, status, refresh } = useSession();
Enter fullscreen mode Exit fullscreen mode

This is the kind of abstraction that makes you question every over-engineered auth setup you've ever built.


Demo

Live App: secure-auth-x.vercel.app

GitHub Repo: github.com/DevCodeHub99/SecureAuthX

The live deployment runs on Vercel Serverless Functions — no long-running Node.js process, no cold-start headaches on the auth routes, and it scales automatically.

The Before vs After (This Is the Real Story)

The

Here's what that table actually means in real-world terms:

Feature Old Version SecureAuthX
Auth Architecture Manual Express JWT logic @custom-auth/core engine
Frontend State Redux Toolkit boilerplate useSession() via @custom-auth/react
Login Methods Password + 6-digit OTP only Password, Magic Links, Auto-login
Account Recovery None Full Forgot/Reset Password flow
Rate Limiting None (brute-forceable) Global (100/15m) + Auth (5/15m)
CSRF Protection None Strict Origin/Referer header checks
Input Validation Frontend only express-validator on backend
Error Handling Raw DB errors to client Generic 500s, safe console logging
Deployment Traditional Node server Vercel Serverless Functions

Every row in that table was a vulnerability or a developer pain point in the original version. Now they're all solved.


The Comeback Story

Let me be real about where this project was.

The original version was a classic "it works on my machine" Express auth app. I had:

  • JWT tokens stored in localStorage (XSS attack waiting to happen)
  • No rate limiting on login routes (a for loop and you're in someone's account)
  • No CSRF protection at all
  • Mongoose validation errors leaking raw database messages to the frontend response
  • A 6-digit OTP system I had wired manually with no time-based expiry logic
  • No password reset. If you forgot it, you were locked out
  • A Redux Toolkit setup with 4+ files just to track "is the user logged in?"

I knew all of this. I left it anyway, because shipping felt more important than getting auth right.

Here's what changed during the Finish-Up-A-Thon:

1. Ripped out the manual JWT logic entirely. Replaced with @custom-auth/core as the auth engine. Tokens are now handled in HTTP-only, Secure, SameSite=Strict cookies. JavaScript in the browser cannot read them at all.

2. Killed Redux Toolkit. The entire auth state layer was replaced by useSession() from @custom-auth/react. The hook handles session fetching, refresh logic, and status tracking. What was 4 files is now one import.

3. Built the Magic Link flow from scratch. Passwordless login via a secure one-time token sent over email, handled through @custom-auth/adapter-nodemailer. The token is single-use, time-bound, and invalidated immediately after verification.

4. Added the auto-login pipeline. After email verification, the backend validates the token, generates a session cookie on the spot, and the frontend calls await refresh() to sync the auth context silently. The user lands in the dashboard without touching a login form.

5. Implemented dual-layer rate limiting. express-rate-limit with two separate limiters: a global limiter for all API traffic (100 req/15min) and a strict auth-specific limiter (5 req/15min) on /api/auth/* routes. Brute force is now a non-issue.

6. Added CSRF protection properly. Every state-mutating request goes through Origin and Referer header validation. No token-in-form approach needed because the session cookie is SameSite=Strict.

7. Replaced error handling completely. All Mongoose and Express errors now pass through a centralized global error handler (Express 5's native async error support). The client gets a generic 500. The actual error logs to the server console. Zero stack trace exposure.

8. Migrated deployment to Vercel Serverless. The traditional long-running Node.js server is gone. Each auth route is a stateless serverless function, which also forced cleaner architecture because you can't rely on in-memory state.

The CSS floating label bug also got fixed: when Chrome or Edge autofills credentials, the label used to overlap the injected text. Fixed it using Tailwind's peer-valid selector combined with React's controlled value={} binding — the label detects injected values and moves out of the way.


My Experience with GitHub Copilot

I'll be honest: Copilot was most useful in the places I expected it least.

The big architectural decisions — switching to @custom-auth/core, restructuring the error pipeline, designing the auto-login flow — those I had to think through myself. Copilot doesn't replace architectural judgment.

But where it genuinely saved hours:

The express-validator chain. Writing validation middleware for every auth route (register, login, forgot-password, reset-password) is repetitive and easy to get wrong. I described the shape of each request and Copilot generated the full validation chain for each endpoint. I reviewed and tweaked, but I didn't start from a blank file.

The rate limiter configuration. Setting up two separate express-rate-limit instances with different windows and maxes, attaching them at the right middleware order, and making the config env-variable-driven — Copilot suggested the full setup in one shot. Accurate.

The CSRF header check utility. The Origin/Referer validation function that runs on every state-mutating request: Copilot generated the base logic from a comment describing what I needed. I hardened it and added the edge cases, but the skeleton was right.

CSS floating label autofill fix. This is the one I was most impressed by. Describing the problem — "browser autofill injects value, label overlaps because peer-placeholder-shown doesn't detect autofill" — Copilot suggested the peer-valid + controlled value approach. That's a niche CSS/React interaction. Getting it right from a description saved real debugging time.

What I learned: Copilot works best when you know exactly what you want and you're just tired of typing it. It's not a senior engineer — it's a fast typist who's read a lot of code. Use it that way and it's genuinely valuable.


Stack Summary

Frontend:  React 18 + Vite + Tailwind CSS v4 + React Router v6
           @custom-auth/react (useSession, useSignIn hooks)

Backend:   Node.js + Express 5 + MongoDB + Mongoose
           @custom-auth/core (auth engine)
           @custom-auth/mongoose (DB adapter)
           @custom-auth/adapter-nodemailer (email pipeline)
           helmet, cors, cookie-parser, express-rate-limit, express-validator

Deploy:    Vercel Serverless Functions
Enter fullscreen mode Exit fullscreen mode

Repo: github.com/DevCodeHub99/SecureAuthX
Live: secure-auth-x.vercel.app


If you're building a MERN app and you're still wiring auth manually — or worse, storing tokens in localStorage — this repo is for you. Clone it, read the architecture notes in the README, and steal whatever you need.

Auth done right isn't complicated. It's just not something most tutorials bother to finish.

This time, I did.

Top comments (0)