Node.js is a popular JavaScript runtime that allows you to build server-side applications using JavaScript. One common use case for Node.js is building web applications, and a key component of many web applications is user authentication.
MongoDB is a popular NoSQL database that is often used with Node.js applications. MongoDB allows you to store data in a flexible, document-based format that can be easily queried using the MongoDB Query Language (MQL).
When building a Node.js authentication system with MongoDB, you typically create a User
model using a MongoDB Object Document Mapper (ODM) like Mongoose. This User
model represents a user in your authentication system and contains properties like email, password, and name.
To authenticate a user, you typically compare the user's inputted password to a hashed password that is stored in the database. You can use a library like bcrypt to hash passwords and compare them to inputted passwords.
To manage authentication sessions, you can use a technique called JSON Web Tokens (JWT). JWTs allow you to generate a token that contains information about the user (such as their user ID), which can be securely transmitted to the client and used to authenticate subsequent requests.
Now we are going to see how to do it step by step:
Step 1: Set up your project
Create a new project directory and navigate into it using your terminal. Then run the following command to create a package.json file:
1
npm init -y
This will initialize a new Node.js project with default settings.
Step 2: Install dependencies
We need to install the following dependencies:
1
npm install express mongoose body-parser jsonwebtoken bcrypt dotenv
Here's a brief description of each dependency:
- Express: A web framework for Node.js
- Mongoose: A MongoDB object modeling tool
- Body-parser: Middleware for handling JSON data
- Jsonwebtoken: A library for creating and verifying JSON web tokens (JWT)
- Bcrypt: A library for hashing and salting passwords
- Dotenv: A library for managing environment variables
Step 3: Create a MongoDB database
Before creating our authentication system, we need to set up a MongoDB database. You can use a local instance of MongoDB or sign up for a free MongoDB Atlas account.
Step 4: Create a .env file
Create a .env file in the root directory of your project and add the following variables:
1
2
3
PORT=3000
MONGODB_URI=<your-mongodb-uri>
JWT_SECRET=<your-jwt-secret>
Make sure to replace <your-mongodb-uri>
and <your-jwt-secret>
with your own values.
Step 5: Create our server.js, the entry point of our app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Import dependencies and app code
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
// Create a new Express app
const app = express();
// Set up middleware
app.use(express.json());
// Connect to MongoDB
mongoose.connect('mongodb://localhost/auth-demo', { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('Connected to MongoDB...'))
.catch(err => console.error('Could not connect to MongoDB...', err));
// Start the server
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}...`));
Step 6: Create a user model
Create a new file called user.js
in a new folder called models
. This will define our user schema and model.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
},
password: {
type: String,
required: true,
minlength: 6,
},
});
userSchema.pre('save', async function(next) {
try {
// Generate a salt
const salt = await bcrypt.genSalt(10);
// Generate a password hash (salt + hash)
const passwordHash = await bcrypt.hash(this.password, salt);
// Re-assign hashed version over original, plain text password
this.password = passwordHash;
next();
} catch (error) {
next(error);
}
});
userSchema.methods.isValidPassword = async function(newPassword) {
try {
return await bcrypt.compare(newPassword, this.password);
} catch (error) {
throw new Error(error);
}
};
const User = mongoose.model('user', userSchema);
module.exports = User;
This model defines an email and password field for our users. We're also using the bcrypt
library to hash and salt passwords.
Step 7: Create an authentication route
Create a new file called auth.js
in a new folder called routes
. This will define our authentication route.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const express = require('express');
const router = express.Router();
const User = require('../models/user');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
router.post('/register', async (req, res) => {
const { name, email, password } = req.body;
try {
// Check if user already exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'User already exists' });
}
// Create a new user
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(password, salt);
const newUser = new User({
name,
email,
password: hashedPassword
});
const savedUser = await newUser.save();
// Create and sign a JWT token
const token = jwt.sign({ userId: savedUser._id }, process.env.JWT_SECRET);
// Return the token and user information
res.json({
token,
user: {
id: savedUser._id,
name: savedUser.name,
email: savedUser.email
}
});
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Server error' });
}
});
module.exports = router;
Step 8: create a login route to verify the jwt token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
router.post('/login', async (req, res, next) => {
const { email, password } = req.body;
// Find the user by email
const user = await User.findOne({ email });
if (!user) {
return res.status(422).json({ error: 'Invalid email or password' });
}
// Check if the password is valid
const isValidPassword = await user.isValidPassword(password);
if (!isValidPassword) {
return res.status(422).json({ error: 'Invalid email or password' });
}
// Generate a JWT token
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET);
// Respond with the token
res.json({ token });
});
This route handles the POST request to /login
. It checks if the user with the given email exists, and then checks if the given password is valid. If the user is authenticated, it generates a JWT token and sends it back to the client.
Step 9: Protect routes with authentication middleware
Now that we have a way to authenticate users, we need to protect routes that require authentication. We'll create a middleware function that checks if the request contains a valid JWT token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const requireAuth = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
jwt.verify(token, process.env.JWT_SECRET, async (err, payload) => {
if (err) {
return res.status(401).json({ error: 'Unauthorized' });
}
const { userId } = payload;
const user = await User.findById(userId);
req.user = user;
next();
});
};
module.exports = requireAuth;
This middleware function checks if the request contains a Authorization
header with a valid JWT token. If the token is valid, it extracts the userId
from the payload and fetches the corresponding user from the database. Finally, it attaches the user object to the request and calls the next
function to continue processing the request.
Step 10: Use the authentication middleware
We can now protect routes that require authentication by using the requireAuth
middleware function. For example, let's create a protected route that returns the user's email:
1
2
3
4
5
6
7
8
9
10
const express = require('express');
const requireAuth = require('../middleware/requireAuth');
const router = express.Router();
router.get('/profile', requireAuth, async (req, res, next) => {
res.json({ email: req.user.email });
});
module.exports = router;
This route is protected by the requireAuth
middleware, which means the user must provide a valid JWT token in the Authorization
header to access it. If the user is authenticated, the route returns the user's email.
That's it! You now have a basic authentication system using Node.js and JWT. Of course, this is just a starting point, and you'll likely want to add more features and security measures to your system.
it was a pleasure helping you with your authentication system using Node.js and JWT! Keep in mind that this is just one way to implement authentication in your application, and there are many other options out there. Make sure to always follow best practices and keep security in mind when dealing with user authentication. If you have any questions or need further assistance, don't hesitate to ask! Good luck with your project!