Authentication
Introduction
Authentication is a crucial part of web application security. It ensures that users are who they say they are, protecting both user data and system integrity. In this blog, we'll dive deep into implementing authentication in a Node.js application using the Express.js framework. We'll cover everything from setting up the project to implementing secure login and registration systems.
Table of Contents
Setting Up the Project
Creating the User Model
Setting Up Authentication Middleware
Implementing User Registration
Implementing User Login
Protecting Routes
Using JSON Web Tokens (JWT)
Logout and Token Invalidation
Best Practices and Security Tips
Setting Up the Project
Step 1: Initialize the Project
First, create a new directory for your project and navigate into it. Then, initialize a new Node.js project.
mkdir auth-tutorial
cd auth-tutorial
npm init -y
Step 2: Install Dependencies
Install the necessary dependencies, including Express, Mongoose (for MongoDB), bcrypt (for hashing passwords), and jsonwebtoken (for handling JWTs).
npm install express mongoose bcrypt jsonwebtoken body-parser
Creating the User Model
We'll use MongoDB to store user data, so let's set up a Mongoose model for our users.
Step 1: Connect to MongoDB
Create a file db.js
to handle the database connection.
// db.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect('mongodb://localhost:27017/authTutorial', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected');
} catch (error) {
console.error(error);
process.exit(1);
}
};
module.exports = connectDB;
Step 2: Define the User Schema
Create a file models/User.js
to define the User schema.
// models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
});
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) {
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
userSchema.methods.matchPassword = async function (password) {
return await bcrypt.compare(password, this.password);
};
const User = mongoose.model('User', userSchema);
module.exports = User;
Setting Up Authentication Middleware
Step 1: Middleware for Handling JSON Web Tokens
Create a file middleware/auth.js
to handle JWT verification.
// middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const protect = async (req, res, next) => {
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' });
}
try {
const decoded = jwt.verify(token, 'your_jwt_secret');
req.user = await User.findById(decoded.id).select('-password');
next();
} catch (error) {
console.error(error);
res.status(401).json({ message: 'Not authorized, token failed' });
}
};
module.exports = { protect };
Implementing User Registration
Step 1: Registration Route
Create a file routes/auth.js
and set up the registration route.
// routes/auth.js
const express = require('express');
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const router = express.Router();
router.post('/register', async (req, res) => {
const { username, password } = req.body;
try {
const user = await User.create({ username, password });
const token = jwt.sign({ id: user._id }, 'your_jwt_secret', {
expiresIn: '30d',
});
res.status(201).json({ token });
} catch (error) {
console.error(error);
res.status(400).json({ message: 'User registration failed' });
}
});
module.exports = router;
Implementing User Login
Step 1: Login Route
In routes/auth.js
, add a route for logging in users.
// routes/auth.js
router.post('/login', async (req, res) => {
const { username, password } = req.body;
try {
const user = await User.findOne({ username });
if (user && (await user.matchPassword(password))) {
const token = jwt.sign({ id: user._id }, 'your_jwt_secret', {
expiresIn: '30d',
});
res.json({ token });
} else {
res.status(401).json({ message: 'Invalid credentials' });
}
} catch (error) {
console.error(error);
res.status(400).json({ message: 'User login failed' });
}
});
Protecting Routes
Step 1: Secure a Route
To protect a route, use the protect
middleware. For example, create a protected route in routes/protected.js
.
// routes/protected.js
const express = require('express');
const { protect } = require('../middleware/auth');
const router = express.Router();
router.get('/profile', protect, (req, res) => {
res.json({
id: req.user._id,
username: req.user.username,
});
});
module.exports = router;
Using JSON Web Tokens (JWT)
JWTs are used to securely transmit information between parties as a JSON object. They are compact, readable, and digitally signed, using a secret or a public/private key pair.
Step 1: Token Creation
In the registration and login routes, we use jwt.sign()
to create a token.
const token = jwt.sign({ id: user._id }, 'your_jwt_secret', {
expiresIn: '30d',
});
Step 2: Token Verification
In the protect
middleware, we use jwt.verify()
to verify the token.
const decoded = jwt.verify(token, 'your_jwt_secret');
Logout and Token Invalidation
Step 1: Implement Logout
While JWTs are stateless and don't require server-side invalidation, you can implement a logout by handling it client-side or maintaining a blacklist of tokens.
Best Practices and Security Tips
Store Secrets Securely: Never hard-code your JWT secret in your source code. Use environment variables instead.
Use HTTPS: Always use HTTPS to protect tokens from being intercepted.
Short-lived Tokens: Use short-lived tokens and refresh them to minimize the risk if a token is compromised.
Validate User Input: Always validate and sanitize user input to prevent injection attacks.
Secure Passwords: Use bcrypt to hash passwords and never store plaintext passwords.
Conclusion
In this detailed guide, we've covered setting up a Node.js and Express.js project, creating a user model, implementing authentication middleware, and creating routes for user registration and login. By following these steps, you can implement a secure authentication system in your web application. Remember to follow best practices to keep your application and user data safe. Happy coding!