Introduction:
Authentication is a fundamental aspect of web development, ensuring that users can securely access protected resources. JSON Web Tokens (JWT) have become a popular choice for implementing authentication in modern web applications due to their simplicity and scalability. In this tutorial, we'll explore how to implement a scalable authentication system using JWT in a MERN (MongoDB, Express.js, React, Node.js) stack application. We'll cover user registration, login, token generation, and secure authorization.
Prerequisites:
Before we begin, make sure you have the following prerequisites installed:
- Node.js and npm (Node Package Manager)
- MongoDB (you can use a local or remote instance)
- React.js (if you're building a frontend)
Setting Up the Backend:
- Initialize a New Node.js Project:
Create a new directory for your project and initialize a new Node.js project by running the following commands:
mkdir mern-authentication
cd mern-authentication
npm init -y
- Install Required Packages:
Install the necessary packages for our backend using the following command:
npm install express mongoose jsonwebtoken bcryptjs body-parser cors
-
express
: Web framework for Node.js. -
mongoose
: MongoDB object modeling tool. -
jsonwebtoken
: Library for generating JWT tokens. -
bcryptjs
: Library for hashing passwords securely. -
body-parser
: Middleware for parsing request bodies. -
cors
: Middleware for enabling Cross-Origin Resource Sharing.
- Create a MongoDB Database:
Set up a MongoDB database either locally or using a cloud service like MongoDB Atlas. Note down the connection URI.
- Create the Backend Server:
Create a file named server.js
and set up a basic Express server with MongoDB connection:
// server.js
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(bodyParser.json());
app.use(cors());
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/mern_auth', {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.log(err));
// Routes
app.use('/api/auth', require('./routes/auth'));
// Start the server
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
- Create Routes for Authentication:
Create a folder named routes
and add a file named auth.js
inside it. This file will contain routes for user registration, login, and token verification.
// routes/auth.js
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const { check, validationResult } = require('express-validator');
// Register a new user
router.post('/register', [
check('name', 'Please enter a name').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 })
], async (req, res) => {
// Validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { name, email, password } = req.body;
try {
// Check if user already exists
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({ msg: 'User already exists' });
}
// Create new user
user = new User({ name, email, password });
// Hash password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
// Save user to database
await user.save();
// Generate JWT token
const payload = {
user: { id: user.id }
};
jwt.sign(payload, 'jwtSecret', { expiresIn: 3600 }, (err, token) => {
if (err) throw err;
res.json({ token });
});
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// Login route
router.post('/login', [
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists()
], async (req, res) => {
// Validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
// Check if user exists
let user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ msg: 'Invalid credentials' });
}
// Compare passwords
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ msg: 'Invalid credentials' });
}
// Generate JWT token
const payload = {
user: { id: user.id }
};
jwt.sign(payload, 'jwtSecret', { expiresIn: 3600 }, (err, token) => {
if (err) throw err;
res.json({ token });
});
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
module.exports = router;
- Create a User Model:
Create a folder named models
and add a file named User.js
inside it. This file will define the user schema and model.
// models/User.js
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model('user', UserSchema);
Conclusion:
In this tutorial, we've implemented a scalable authentication system using JSON Web Tokens (JWT) in a MERN stack application. We've covered user registration, login, token generation, and secure authorization. This authentication system provides a solid foundation for building secure and scalable web applications. Feel free to extend this implementation by adding features like password reset, email verification, and role-based access control.
Top comments (0)