A complete authentication system built with React, Node.js, Express, and MySQL. Features user registration, login, JWT authentication, and a modern, responsive UI with custom CSS.
- Features
- Tech Stack
- Project Structure
- Prerequisites
- Installation
- Configuration
- Running the Application
- API Documentation
- Screenshots
- Security Features
- Troubleshooting
- Contributing
- License
- ✅ User registration with email validation
- ✅ Secure password hashing with bcrypt
- ✅ JWT token-based authentication
- ✅ Password visibility toggle
- ✅ Form validation (client & server-side)
- ✅ Protected routes with middleware
- ✅ Modern, clean design with custom CSS
- ✅ Fully responsive (mobile, tablet, desktop)
- ✅ Smooth animations and transitions
- ✅ Real-time error/success messages
- ✅ Loading states for better UX
- ✅ Keyboard navigation support
- ✅ Password encryption (bcrypt)
- ✅ JWT token authentication
- ✅ SQL injection prevention
- ✅ CORS configuration
- ✅ Input validation & sanitization
- ✅ Secure session management
- React (v18+) - UI Library
- Lucide React - Icon library
- Custom CSS - Styling (no framework dependencies)
- Node.js - Runtime environment
- Express.js - Web framework
- MySQL2 - Database driver
- bcryptjs - Password hashing
- jsonwebtoken - JWT authentication
- dotenv - Environment variables
- cors - Cross-origin resource sharing
- MySQL (via MySQL Workbench or XAMPP)
project-root/
│
├── auth-backend/ # Backend API
│ ├── config/
│ │ └── database.js # Database connection
│ ├── controllers/
│ │ └── authController.js # Authentication logic
│ ├── middleware/
│ │ └── authMiddleware.js # JWT verification
│ ├── routes/
│ │ └── authRoutes.js # API routes
│ ├── .env # Environment variables
│ ├── server.js # Entry point
│ └── package.json
│
├── auth-frontend/ # React frontend
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Login.jsx # Login component
│ │ │ └── Register.jsx # Register component
│ │ ├── styles/
│ │ │ └── auth.css # Authentication styles
│ │ ├── api/
│ │ │ └── auth.js # API helper (optional)
│ │ ├── App.js # Main component
│ │ ├── App.css # App styles
│ │ ├── index.js # Entry point
│ │ └── index.css # Global styles
│ └── package.json
│
├── screenshot.png # Application screenshot
└── README.md # This file
Before you begin, ensure you have the following installed:
- Node.js (v14 or higher) - Download
- npm (comes with Node.js) or yarn
- MySQL (via XAMPP or standalone) - Download XAMPP
- MySQL Workbench (recommended) - Download
- Git (optional) - Download
node --version # Should be v14 or higher
npm --version # Should be 6 or higher
mysql --version # Should be 5.7 or higherUsing XAMPP:
- Open XAMPP Control Panel
- Start Apache and MySQL modules
- MySQL will run on port
3306by default
Using Standalone MySQL:
- MySQL should be running as a service automatically
- Open MySQL Workbench
- Connect to your local MySQL server (
localhost:3306) - Enter password if prompted (XAMPP default is empty or
root) - Click Query tab or press
Ctrl+T - Copy and paste the following SQL:
-- Create the database
CREATE DATABASE IF NOT EXISTS auth_system;
-- Use the database
USE auth_system;
-- Create users table
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Create index on email for faster lookups
CREATE INDEX idx_email ON users(email);- Click the lightning bolt icon ⚡ or press
Ctrl+Shift+Enter - Verify success in the Action Output panel
-- Check if database exists
SHOW DATABASES;
-- Check tables
USE auth_system;
SHOW TABLES;
-- Check table structure
DESCRIBE users;Expected Output:
+------------+--------------+------+-----+-------------------+
| Field | Type | Null | Key | Default |
+------------+--------------+------+-----+-------------------+
| id | int | NO | PRI | NULL |
| email | varchar(255) | NO | UNI | NULL |
| password | varchar(255) | NO | | NULL |
| created_at | timestamp | YES | | CURRENT_TIMESTAMP |
| updated_at | timestamp | YES | | CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+
mkdir auth-backend
cd auth-backend
npm init -ynpm install express mysql2 bcryptjs jsonwebtoken dotenv cors body-parser
npm install nodemon --save-devDependencies Installed:
express- Web frameworkmysql2- MySQL database driverbcryptjs- Password hashingjsonwebtoken- JWT token generationdotenv- Environment variablescors- Cross-origin requestsbody-parser- Parse request bodiesnodemon- Auto-restart server (dev)
Create the following directory structure:
mkdir config controllers middleware routesCreate these files in their respective folders:
.env (root of auth-backend):
PORT=5000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=
DB_NAME=auth_system
JWT_SECRET=your_super_secret_jwt_key_change_this_in_production_12345
JWT_EXPIRE=7d
⚠️ Important: ChangeJWT_SECRETto a random, secure string in production!
config/database.js:
const mysql = require('mysql2');
require('dotenv').config();
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
const promisePool = pool.promise();
// Test connection
pool.getConnection((err, connection) => {
if (err) {
console.error('❌ Database connection failed:', err.message);
} else {
console.log('✅ Database connected successfully');
connection.release();
}
});
module.exports = promisePool;controllers/authController.js:
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const db = require('../config/database');
// Sign Up Controller
exports.signUp = async (req, res) => {
try {
const { email, password } = req.body;
// Validation
if (!email || !password) {
return res.status(400).json({ message: 'Email and password are required' });
}
if (password.length < 6) {
return res.status(400).json({ message: 'Password must be at least 6 characters' });
}
// Check if user already exists
const [existingUsers] = await db.execute(
'SELECT * FROM users WHERE email = ?',
[email]
);
if (existingUsers.length > 0) {
return res.status(409).json({ message: 'Email already registered' });
}
// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
// Insert user
const [result] = await db.execute(
'INSERT INTO users (email, password) VALUES (?, ?)',
[email, hashedPassword]
);
res.status(201).json({
message: 'Account created successfully! Please sign in.',
userId: result.insertId
});
} catch (error) {
console.error('SignUp Error:', error);
res.status(500).json({ message: 'Server error during registration' });
}
};
// Sign In Controller
exports.signIn = async (req, res) => {
try {
const { email, password } = req.body;
// Validation
if (!email || !password) {
return res.status(400).json({ message: 'Email and password are required' });
}
// Find user
const [users] = await db.execute(
'SELECT * FROM users WHERE email = ?',
[email]
);
if (users.length === 0) {
return res.status(401).json({ message: 'Invalid email or password' });
}
const user = users[0];
// Check password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ message: 'Invalid email or password' });
}
// Generate JWT token
const token = jwt.sign(
{ id: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE }
);
res.status(200).json({
message: 'Login successful',
token,
user: {
id: user.id,
email: user.email,
createdAt: user.created_at
}
});
} catch (error) {
console.error('SignIn Error:', error);
res.status(500).json({ message: 'Server error during login' });
}
};
// Get User Profile (Protected Route Example)
exports.getProfile = async (req, res) => {
try {
const userId = req.user.id;
const [users] = await db.execute(
'SELECT id, email, created_at FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
return res.status(404).json({ message: 'User not found' });
}
res.status(200).json({ user: users[0] });
} catch (error) {
console.error('GetProfile Error:', error);
res.status(500).json({ message: 'Server error' });
}
};middleware/authMiddleware.js:
const jwt = require('jsonwebtoken');
exports.protect = async (req, res, next) => {
try {
let token;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return res.status(401).json({ message: 'Not authorized, no token' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
console.error('Auth Middleware Error:', error);
res.status(401).json({ message: 'Not authorized, token failed' });
}
};routes/authRoutes.js:
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const { protect } = require('../middleware/authMiddleware');
// Public routes
router.post('/signup', authController.signUp);
router.post('/signin', authController.signIn);
// Protected routes
router.get('/profile', protect, authController.getProfile);
module.exports = router;server.js:
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
require('dotenv').config();
const authRoutes = require('./routes/authRoutes');
const app = express();
// Middleware
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Routes
app.use('/api/auth', authRoutes);
// Health check route
app.get('/api/health', (req, res) => {
res.json({ status: 'Server is running', timestamp: new Date() });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: 'Something went wrong!' });
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ message: 'Route not found' });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
console.log(`📍 http://localhost:${PORT}`);
});{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}# Navigate to project root (exit auth-backend folder)
cd ..
# Create React app
npx create-react-app auth-frontend
cd auth-frontendnpm install lucide-react# Create folders
mkdir src/pages
mkdir src/styles
mkdir src/apiCopy the following files from the artifacts provided earlier:
src/pages/Login.jsx - Login component
src/pages/Register.jsx - Register component
src/styles/auth.css - Authentication styles
src/App.js - Main app component
src/App.css - App styles
src/index.css - Global styles
src/index.js should be:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 5000 |
DB_HOST |
MySQL host | localhost |
DB_USER |
MySQL username | root |
DB_PASSWORD |
MySQL password | (empty for XAMPP) |
DB_NAME |
Database name | auth_system |
JWT_SECRET |
JWT secret key | (change in production!) |
JWT_EXPIRE |
Token expiration | 7d |
Update API endpoint in components if needed:
const API_URL = 'http://localhost:5000/api/auth';- XAMPP: Start MySQL in XAMPP Control Panel
- Standalone: Ensure MySQL service is running
cd auth-backend
npm run devExpected Output:
✅ Database connected successfully
🚀 Server running on port 5000
📍 http://localhost:5000
Open a new terminal window:
cd auth-frontend
npm startExpected Output:
Compiled successfully!
You can now view auth-frontend in the browser.
Local: http://localhost:3000
On Your Network: http://192.168.x.x:3000
Navigate to: http://localhost:3000
http://localhost:5000/api/auth
POST /api/auth/signupRequest Body:
{
"email": "user@example.com",
"password": "password123"
}Response (Success - 201):
{
"message": "Account created successfully! Please sign in.",
"userId": 1
}Response (Error - 409):
{
"message": "Email already registered"
}POST /api/auth/signinRequest Body:
{
"email": "user@example.com",
"password": "password123"
}Response (Success - 200):
{
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"email": "user@example.com",
"createdAt": "2024-01-15T10:30:00.000Z"
}
}Response (Error - 401):
{
"message": "Invalid email or password"
}GET /api/auth/profileHeaders:
Authorization: Bearer YOUR_JWT_TOKEN_HERE
Response (Success - 200):
{
"user": {
"id": 1,
"email": "user@example.com",
"created_at": "2024-01-15T10:30:00.000Z"
}
}Response (Error - 401):
{
"message": "Not authorized, no token"
}GET /api/healthResponse (200):
{
"status": "Server is running",
"timestamp": "2024-01-15T10:30:00.000Z"
}- Bcrypt Hashing: Passwords are hashed with salt (10 rounds)
- Minimum Length: 6 characters required
- Never Stored Plain: Passwords never stored in plain text
- Secure Tokens: JWT tokens for stateless authentication
- Expiration: Tokens expire after 7 days
- HTTP-Only: Can be configured for HTTP-only cookies
- Prepared Statements: Protection against SQL injection
- Unique Constraints: Email uniqueness enforced at DB level
- Indexed Queries: Optimized with email index
- Input Validation: All inputs validated on server
- CORS Configuration: Cross-origin requests controlled
- Error Handling: Generic error messages (no info leakage)
- Rate Limiting: Can be added with
express-rate-limit
- No localStorage: Tokens stored in sessionStorage
- XSS Prevention: React's built-in XSS protection
- HTTPS Ready: Can be deployed with SSL/TLS
Error: ER_ACCESS_DENIED_ERROR
Solution:
- Check MySQL is running
- Verify credentials in
.env - Test connection in MySQL Workbench
Error: listen EADDRINUSE: address already in use :::5000
Solution:
# Find process using port 5000
lsof -i :5000 # Mac/Linux
netstat -ano | findstr :5000 # Windows
# Kill the process or change PORT in .envError: Cannot find module 'express'
Solution:
cd auth-backend
npm installAccess to fetch has been blocked by CORS policy
Solution:
- Ensure backend has
app.use(cors())enabled - Check backend is running on correct port
Module not found: Can't resolve 'lucide-react'
Solution:
cd auth-frontend
npm install lucide-reactConnection error. Please try again.
Solution:
- Verify backend is running (
http://localhost:5000) - Check API endpoint URLs in components
- Check browser console for detailed errors
Error: Table 'auth_system.users' doesn't exist
Solution:
- Open MySQL Workbench
- Run the CREATE TABLE SQL script again
- Verify with
SHOW TABLES;
Error: ER_DUP_ENTRY: Duplicate entry for key 'email'
Solution:
- This email is already registered
- Use a different email or delete the existing user:
DELETE FROM users WHERE email = 'test@example.com';- Navigate to
http://localhost:3000 - Click "Sign Up"
- Enter:
- Email:
test@example.com - Password:
password123 - Confirm Password:
password123
- Email:
- Click "SIGN UP"
- Should see: "Account created successfully!"
- Click "Sign In"
- Enter same credentials
- Click "SIGN IN"
- Should see: "Login successful!"
- Check browser console - token should be logged
- Copy token from login response
- Create GET request to
http://localhost:5000/api/auth/profile - Add header:
Authorization: Bearer YOUR_TOKEN - Send request
- Should receive user profile data
# Install Heroku CLI
npm install -g heroku
# Login
heroku login
# Create app
heroku create your-app-name
# Set environment variables
heroku config:set DB_HOST=your-mysql-host
heroku config:set DB_USER=your-db-user
heroku config:set DB_PASSWORD=your-db-password
heroku config:set JWT_SECRET=your-secret-key
# Deploy
git push heroku main# Build production version
npm run build
# Deploy to Netlify (drag & drop build folder)
# Or use Netlify CLI
npm install -g netlify-cli
netlify deploy --prodBackend (.env):
PORT=5000
DB_HOST=your-production-db-host
DB_USER=your-production-user
DB_PASSWORD=your-secure-password
DB_NAME=auth_system
JWT_SECRET=your-very-long-random-secure-secret-key
JWT_EXPIRE=7d
NODE_ENV=productionFrontend: Update API URL to production backend:
const API_URL = 'https://your-backend-api.com/api/auth';- Password reset functionality
- Email verification
- OAuth (Google, Facebook, GitHub)
- Two-factor authentication (2FA)
- User profile management
- Remember me functionality
- Rate limiting for API endpoints
- Refresh token mechanism
- User roles and permissions
- Account deletion
- Password strength indicator
- Email notifications
- Activity logs
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Anila Nawaz
- GitHub: @AnilaAnilaN
- Email: anilanawaz531@gmail.com
- React - UI Library
- Express - Backend Framework
- MySQL - Database
- Lucide React - Icon Library
- bcryptjs - Password Hashing
- jsonwebtoken - JWT Authentication
If you have any questions or issues, please:
- Check the Troubleshooting section
- Search existing issues on GitHub
- Create a new issue with detailed information
If you found this project helpful, please give it a ⭐ on GitHub!
Made with ❤️ and ☕
