Authentication is a crucial component in modern web applications, allowing developers to ensure that only authorized users can access specific features or data. In a Node.js and Express-based application, authentication is typically handled using tokens, most commonly JSON Web Tokens (JWT), due to their efficiency and security.
In this guide, we'll explore how to set up user authentication in a Node.js and Express application using JWTs. By the end, you'll have a solid understanding of how to implement secure authentication for your own projects.
What is Authentication?
Authentication is the process of verifying the identity of a user or system. In web applications, it involves checking user credentials like a username and password. Upon successful verification, the system allows the user to interact with the application. For enhanced security, token-based authentication such as JWTs are commonly used.
Why Use JSON Web Tokens (JWT)?
JWTs are an industry standard (RFC 7519) for secure and stateless token-based authentication. They allow information to be transmitted securely between parties as a JSON object. Tokens are often used to verify a user's identity without needing to store session data on the server, making JWT a great choice for stateless applications.
Step-by-Step: Implementing Authentication in Node.js and Express
Let’s break down the implementation of a basic authentication system using JWT in Node.js and Express.
1. Setting Up Your Node.js Application
Before diving into authentication, we need to set up a basic Node.js and Express application. Follow these steps to initialize your project:
mkdir auth-demo
cd auth-demo
npm init -y
npm install express bcryptjs jsonwebtoken mongoose dotenv
Here’s what each dependency is for:
- express: Framework for building our Node.js server.
- bcryptjs: To hash and compare passwords securely.
- jsonwebtoken: For generating and verifying JWTs.
- mongoose: To interact with MongoDB.
- dotenv: For managing environment variables like secrets and database connection strings.
2. Configuring Environment Variables
Create a .env
file in the root directory of your project to store sensitive information like your database URI and JWT secret key.
MONGODB_URI=mongodb://localhost:27017/auth-demo
JWT_SECRET=your_jwt_secret_key
3. Connecting to MongoDB
In the project’s root, create a db.js
file inside a config
folder to handle the MongoDB connection.
// config/db.js
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected');
} catch (err) {
console.error('Error connecting to MongoDB:', err.message);
process.exit(1);
}
};
module.exports = connectDB;
4. Creating the User Model
Next, create a User model to define the structure of user documents in MongoDB. Inside a models
folder, create User.js
:
// models/User.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
});
module.exports = mongoose.model('User', userSchema);
5. Implementing User Registration
We’ll now set up the route for user registration. In the controllers
folder, create a file called authController.js
and implement the registration logic.
// controllers/authController.js
const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
// User registration
exports.register = async (req, res) => {
const { username, password } = req.body;
try {
const existingUser = await User.findOne({ username });
if (existingUser) {
return res.status(400).json({ message: 'Username already exists' });
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = new User({ username, password: hashedPassword });
await newUser.save();
res.status(201).json({ message: 'User registered successfully' });
} catch (err) {
res.status(500).json({ error: err.message });
}
};
This logic hashes the password using bcrypt before storing the user’s information in MongoDB.
6. Implementing User Login
Login is crucial for generating and returning JWTs, which the client will use to authenticate future requests. Here’s how to implement the login logic:
// controllers/authController.js (continue)
exports.login = async (req, res) => {
const { username, password } = req.body;
try {
const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ message: 'Invalid username or password' });
}
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ message: 'Invalid username or password' });
}
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
} catch (err) {
res.status(500).json({ error: err.message });
}
};
If the login is successful, we generate a JWT using jsonwebtoken and send it to the client.
7. Setting Up Middleware for Protected Routes
JWTs are useful for protecting routes that require authentication. We’ll create middleware to verify the token and ensure only authorized users can access specific endpoints.
// middleware/authMiddleware.js
const jwt = require('jsonwebtoken');
exports.verifyToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) return res.sendStatus(403);
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};
8. Applying Authentication Middleware
Finally, let’s apply the middleware to protect a route. For example, you might want users to access their profile only after logging in:
// routes/userRoutes.js
const express = require('express');
const { verifyToken } = require('../middleware/authMiddleware');
const { getUserProfile } = require('../controllers/userController');
const router = express.Router();
router.get('/profile', verifyToken, getUserProfile);
module.exports = router;
The verifyToken
middleware checks for a valid JWT in the request’s headers and allows access to the route if the token is verified.
Conclusion
In this guide, we covered the essentials of implementing user authentication using JWT in a Node.js and Express application. We walked through setting up user registration, login, and protecting routes using token-based authentication. With this foundation, you can build robust, secure authentication systems in your own applications. As you continue to develop, consider adding refresh tokens, password reset functionality, and multi-factor authentication for enhanced security.
By mastering authentication with Node.js and Express, you're well on your way to building scalable, secure web applications.
Top comments (0)