This project provides a reusable authentication module with:
- Frontend: React, Vite, TypeScript, Zustand, Axios
- Backend: Express, MongoDB, TypeScript, JWT
It supports user registration, login, logout, and token refresh using JWT access tokens and HttpOnly refresh token cookies.
src/api/axiosInstance.ts: Axios instance with interceptors for token refreshsrc/store/authStore.ts: Zustand store for authentication statesrc/components/Login.tsx: Login formsrc/components/Register.tsx: Registration formsrc/App.tsx: Routing and main app component
src/controllers/auth.ts: Authentication controllers (login, register, logout, refresh)src/models/User.ts: Mongoose user schemasrc/routes/auth.ts: Authentication routessrc/index.ts: Express server setup
- Node.js (v18 or higher)
- pnpm (v8 or higher)
- MongoDB (local or cloud via MongoDB Atlas)
-
Navigate to the
server/directory and install dependencies:cd server pnpm install -
Create a
.envfile inserver/:MONGO_URI=mongodb://localhost:27017/auth-module PORT=5000 JWT_SECRET=your_jwt_secret_here NODE_ENV=development
- Replace
your_jwt_secret_herewith a secure string (e.g., runopenssl rand -base64 32). - Update
MONGO_URIfor remote MongoDB instances.
- Replace
-
Start the backend:
pnpm dev
The server runs at
http://localhost:5000.
-
Navigate to the
client/directory and install dependencies:cd client pnpm install -
Create a
.envfile inclient/:VITE_BACKEND_URL=http://localhost:5000
-
Start the frontend:
pnpm run dev
The app runs at
http://localhost:5173.
- Register: POST
/api/auth/registerwithname,email,password. ReturnsaccessTokenand sets HttpOnlyrefreshTokencookie. - Login: POST
/api/auth/loginwithemail,password. ReturnsaccessTokenand sets HttpOnlyrefreshTokencookie. - Refresh: POST
/api/auth/refreshwithrefreshTokencookie. Returns newaccessToken. - Logout: POST
/api/auth/logout. ClearsrefreshTokencookie and auth state. - Frontend State: Managed by Zustand in
authStore.ts. StoresisAuthenticated,accessToken, anduser(name, email). - Token Handling:
accessToken: Stored in-memory (Zustand), sent inAuthorization: Bearerheader.refreshToken: HttpOnly cookie, sent withwithCredentials: true.
- Routing:
/login: Login page. Redirects to/if authenticated./register: Registration page. Redirects to/if authenticated./: Protected route. Redirects to/loginif unauthenticated.
To use this auth module in another repository:
-
Copy
server/src/to your project’s backend directory. -
Merge
server/package.jsondependencies:"dependencies": { "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.14.1" }, "devDependencies": { "@types/bcryptjs": "^2.4.6", "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.7", "@types/node": "^22.10.7", "concurrently": "^8.2.2", "nodemon": "^3.1.10", "typescript": "^5.7.3" }
-
Add
.envconfiguration to your backend:MONGO_URI=mongodb://localhost:27017/your-db PORT=5000 JWT_SECRET=your_jwt_secret_here NODE_ENV=development
-
Add routes to your Express app:
import authRoutes from './routes/auth.js'; app.use('/api/auth', authRoutes);
-
Copy
client/src/api/,client/src/store/,client/src/components/Login.tsx, andclient/src/components/Register.tsxto your frontend. -
Merge
client/package.jsondependencies:"dependencies": { "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.2", "zustand": "^5.0.0" }, "devDependencies": { "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.2", "typescript": "^5.5.2", "vite": "^5.4.8" }
-
Add
.envto your frontend:VITE_BACKEND_URL=http://localhost:5000
-
Integrate routes in your
App.tsx:import { Routes, Route, Navigate } from 'react-router-dom'; import Login from './components/Login'; import Register from './components/Register'; import { useAuthStore } from './store/authStore'; function App() { const isAuthenticated = useAuthStore((state) => state.isAuthenticated); return ( <Routes> <Route path="/login" element={isAuthenticated ? <Navigate to="/" replace /> : <Login />} /> <Route path="/register" element={isAuthenticated ? <Navigate to="/" replace /> : <Register />} /> <Route path="/" element={isAuthenticated ? <YourProtectedComponent /> : <Navigate to="/login" replace />} /> </Routes> ); }
-
Ensure backend CORS allows your frontend origin:
app.use(cors({ origin: 'http://localhost:5173', credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], }));
-
Run both backend and frontend:
cd server && pnpm dev cd client && pnpm run dev
-
Test
/login,/register,/api/auth/refresh, and/api/auth/logout.
-
Verify
client/.envhas:VITE_BACKEND_URL=http://localhost:5000
-
Check
axiosInstance.tsuses:const baseURL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000/api';
-
Inspect Network tab for incorrect URLs.
-
Ensure
server/src/index.tshas:app.use(cors({ origin: 'http://localhost:5173', credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], }));
-
Check console for CORS errors and verify
access-control-allow-credentials: true.
-
Confirm
refreshTokencookie is set (dev tools > Application > Cookies). -
Verify
withCredentials: trueinaxiosInstance.ts:withCredentials: true -
Check backend logs for:
Refresh parsed cookies:
-
Log
isAuthenticatedinApp.tsxandLogin.tsx:console.log('isAuthenticated:', isAuthenticated);
-
Ensure
/api/auth/refreshreturns 200 withaccessToken.
-
Verify MongoDB is running and
MONGO_URIis correct. -
Check user document:
mongo use auth-module db.users.find({ email: "[email protected]" })
- Frontend: Uses React 18, Vite, TypeScript, Zustand for state, Axios for API calls, and Tailwind CSS for styling.
- Backend: Uses Express, MongoDB (Mongoose), TypeScript, JWT for tokens, and
cookie-parserfor HttpOnly cookies. - Security:
accessToken: In-memory (Zustand), short-lived (1 hour).refreshToken: HttpOnly, secure (in production), 7-day expiry.- Passwords: Hashed with bcrypt.
- Production:
-
Set
NODE_ENV=productionandsecure: trueinserver/src/controllers/auth.ts:secure: process.env.NODE_ENV === 'production'
-
Update
VITE_BACKEND_URLto your production backend URL.
-