Imagine you’re building a web application that's about to launch. You've carefully designed the user interface, added exciting features, and made sure everything runs smoothly. But as the launch date gets closer, a nagging concern starts to worry you—security. Specifically, how to ensure that only the right users can access the right parts of your application. This is where authentication comes in.
Authentication is the process of verifying who a user is, and it's a critical aspect of web development. In the vast digital landscape, ensuring that users can securely log in and log out of your application is paramount. One slip, and your app could be vulnerable to attacks, putting user data at risk.
In this article, we will explore secure authentication in Node.js, using bcrypt.js to hash passwords and JWT tokens to manage user sessions. By the end, you'll have a solid understanding of how to implement a strong login/logout system, keeping your users’ data safe and secure.
So, let’s embark on this journey to build a bulletproof authentication system, starting from setting up our environment to securing our routes with JWT. Ready to lock down your Node.js app? Let’s get started.
First, initialize your Node.js project with npm init -y, which creates a package.json file with default settings. Next, install essential packages: express for setting up the server, mongoose for managing MongoDB, jsonwebtoken for handling JWT tokens, bcryptjs for hashing passwords, dotenv for environment variables, cors for enabling Cross-Origin Resource Sharing, cookie-parser for parsing cookies. Finally, add nodemon as a development dependency to automatically restart the server when code changes.
1.`npm init -y` 2.`npm install express mongoose jsonwebtoken bcryptjs dotenv cors cookie-parser` 3.`npm install nodemon -D`
Now modify the package.json file. Add scripts like my code and type.
"scripts": { "dev": "nodemon backend/index.js", "start": "node backend/index.js" }, "type": "module",
Next, we'll set up a basic Express server. Create a file named index.js . This code initializes Express and creates an instance of the application. We'll then define a route for the root URL ("/") to handle incoming HTTP GET requests. After that, we'll start the server on port 8000, allowing it to listen for incoming requests.
import express from "express"; const app = express(); app.get("/", (req, res) => { res.send("Server is ready"); }); app.listen(8000, () => { console.log("Server is running on PORT 8000"); });
Now, we will create a folder named 'routes' and in that folder we will make make a new file named authRoute.js and paste the below code to see basics of routes.
In this code snippet, we're setting up routes for different authentication endpoints using Express. First, we import the express library and create a new router instance. Then, we define three GET routes: /signup, /login, and /logout, each responding with a JSON object indicating that the respective endpoint was hit. Finally, we export the router instance as the default export, making it available for use in other parts of the application.
1.`npm init -y` 2.`npm install express mongoose jsonwebtoken bcryptjs dotenv cors cookie-parser` 3.`npm install nodemon -D`
Now change the index.js adding the auth route to test out your end points.
"scripts": { "dev": "nodemon backend/index.js", "start": "node backend/index.js" }, "type": "module",
Now, you can test it in your browser...but I will use Postman for its convenience. You can test all the end points like this.
Similarly you can see the other routes like Logout and SignUp.
So, Our basic app is ready...now make it a robust and a proper authentication system.
Now, first ready our mongoDB database. To do that make a folder Model and under that a file User.js and in this file add Mongoose schema and model for a User in a mongoDB database. The schema includes fields for username, fullName, password, and email, each with specified data types and constraints like uniqueness and required status. The password field also has a minimum length of 6 characters.
import express from "express"; const app = express(); app.get("/", (req, res) => { res.send("Server is ready"); }); app.listen(8000, () => { console.log("Server is running on PORT 8000"); });
Now let's connect to our database. We'll create a folder named db and inside it, a file called connectDB.js. In this file, we'll define an asynchronous function connectMongoDB that tries to connect to a MongoDB database using Mongoose. It gets the database connection string from the MONGO_URI environment variable. If the connection is successful, it logs a success message with the host name. If it fails, it logs the error and exits the process with a status code of 1. The function is exported for use in other parts of the application.
import express from "express"; // Create a new Express router instance const router = express.Router(); // Define a GET route for the signup endpoint router.get("/signup", (req, res) => { // Return a JSON response indicating that the signup endpoint was hit res.json({ data: "You hit signup endpoint", }); }); // Define a GET route for the login endpoint router.get("/login", (req, res) => { // Return a JSON response indicating that the login endpoint was hit res.json({ data: "You hit login endpoint", }); }); // Define a GET route for the logout endpoint router.get("/logout", (req, res) => { // Return a JSON response indicating that the logout endpoint was hit res.json({ data: "You hit logout endpoint", }); }); // Export the router instance as the default export export default router;
Now to use MONGO_URI we have to make it in .env file. Here I have used local mongoDB setup connection string. If you want then you can also use mongoDB atlas.
import express from "express"; import authRoute from "./routes/authRoutes.js"; const app = express(); app.get("/", (req, res) => { res.send("Server is ready"); }); app.use("/api/auth", authRoute); app.listen(8000, () => { console.log("Server is running on PORT 8000"); });
Now make the signup function. For this 1st make a folder controller and there file authController.js
import mongoose from "mongoose"; // Define the User schema with various fields and their data types const userSchema = new mongoose.Schema( { // The unique username of the user username: { type: String, required: true, unique: true, }, fullName: { type: String, required: true, }, // The password of the user (min length: 6) password: { type: String, required: true, minLength: 6, }, // The email of the user (unique) email: { type: String, required: true, unique: true, }, }, { timestamps: true } ); // Create the User model based on the userSchema const User = mongoose.model("User", userSchema); // Export the User model export default User;
First, it extracts fullName, username, email, and password from the request body. It validates the email format using a regular expression, returning a 400 status if the format is invalid.
Next, the function checks if the username or email already exists in the database. If either is taken, a 400 status with an error message is returned. It also ensures the password is at least 6 characters long, sending another 400 status if this condition isn't met.
The password is then securely hashed using bcrypt. A new User instance is created with the provided data and saved to the database.
After saving, the function generates a JWT token, sets it as a cookie, and returns a 201 status with the user's ID, full name, username, and email. If any errors occur, they are logged, and a 500 status is sent with an "Internal Server Error" message.
To make this function active you have to import these
1.`npm init -y` 2.`npm install express mongoose jsonwebtoken bcryptjs dotenv cors cookie-parser` 3.`npm install nodemon -D`
Notice something? a new thing called generateTokenAndSetCookie...lets see its code...make a folder utils and there generateTokenAndSetCookie.js.
"scripts": { "dev": "nodemon backend/index.js", "start": "node backend/index.js" }, "type": "module",
The **generateTokenAndSetCookie **function creates a JWT and stores it in a cookie for user authentication.
JWT Generation:
The function uses the jsonwebtoken library to create a JWT. It signs the token with the user's ID and a secret key (JWT_SECRET from the environment variables), setting it to expire in 15 days.
Setting the Cookie:
The token is then stored in a cookie on the user's browser. The cookie is configured with several security attributes:
So this function ensures that the user's session is both secure and persistent, making it a crucial part of the authentication process.
Here we have to add another environment variable JWT_SECRET in .env. You can add any type of mix of number and string like this.
import express from "express"; const app = express(); app.get("/", (req, res) => { res.send("Server is ready"); }); app.listen(8000, () => { console.log("Server is running on PORT 8000"); });
Now our signUp function is complete..so make its route now.
import express from "express"; // Create a new Express router instance const router = express.Router(); // Define a GET route for the signup endpoint router.get("/signup", (req, res) => { // Return a JSON response indicating that the signup endpoint was hit res.json({ data: "You hit signup endpoint", }); }); // Define a GET route for the login endpoint router.get("/login", (req, res) => { // Return a JSON response indicating that the login endpoint was hit res.json({ data: "You hit login endpoint", }); }); // Define a GET route for the logout endpoint router.get("/logout", (req, res) => { // Return a JSON response indicating that the logout endpoint was hit res.json({ data: "You hit logout endpoint", }); }); // Export the router instance as the default export export default router;
ok, now let’s modify our index.js Here we added some new imports. dotenv: Loads environment variables securely from .env; express.json(): Parses incoming JSON requests; express.urlencoded({ extended: true }): Parses URL-encoded data; cookieParser: Handles cookies for JWT tokens; connectMongoDB(): Connects to MongoDB for data storage; Routes: /api/auth manages signup, login, and logout.
Here is updated code of index.js
1.`npm init -y` 2.`npm install express mongoose jsonwebtoken bcryptjs dotenv cors cookie-parser` 3.`npm install nodemon -D`
So. now it’s time to test our signup function in Postman. Let’s see if it’s working or not.
So, here is the results.
Here you can see it’s working properly and you can check it your mongoDB database as well.
Now make the login function. Let’s go again to our authController.js file
"scripts": { "dev": "nodemon backend/index.js", "start": "node backend/index.js" }, "type": "module",
The login controller authenticates a user by verifying their username and password. It first searches for the user in the database using the username. If found, it compares the provided password with the hashed password stored in the database using bcrypt. If the username or password is incorrect, it returns an error response. On successful verification, it generates a JWT token, sets it as a cookie using generateTokenAndSetCookie, and responds with a success message, indicating the user is logged in successfully.
Let’s add our login route in authRoutes.js
import express from "express"; const app = express(); app.get("/", (req, res) => { res.send("Server is ready"); }); app.listen(8000, () => { console.log("Server is running on PORT 8000"); });
Let’s test it in Postman.
Here you can see it is successfully showing Logged in.
Okay. Now the last function i.e. the logout function. Let’s implement this. It’s pretty simple.
import express from "express"; // Create a new Express router instance const router = express.Router(); // Define a GET route for the signup endpoint router.get("/signup", (req, res) => { // Return a JSON response indicating that the signup endpoint was hit res.json({ data: "You hit signup endpoint", }); }); // Define a GET route for the login endpoint router.get("/login", (req, res) => { // Return a JSON response indicating that the login endpoint was hit res.json({ data: "You hit login endpoint", }); }); // Define a GET route for the logout endpoint router.get("/logout", (req, res) => { // Return a JSON response indicating that the logout endpoint was hit res.json({ data: "You hit logout endpoint", }); }); // Export the router instance as the default export export default router;
The logout controller securely logs out a user by clearing the JWT cookie from the client's browser using res.cookie, setting its value to an empty string and its maxAge to 0, ensuring immediate expiration. Upon successful cookie clearance, it sends a success response with a message indicating the user is logged out successfully. If any error occurs during this process, it catches the error, logs it, and returns an Internal Server Error response.
Add this route to our authRoute.js
import express from "express"; import authRoute from "./routes/authRoutes.js"; const app = express(); app.get("/", (req, res) => { res.send("Server is ready"); }); app.use("/api/auth", authRoute); app.listen(8000, () => { console.log("Server is running on PORT 8000"); });
okay. Let’s test our last feature, if it’s working fine or not.
Oh!…It’s working super fine. ??
So, now our complete backend of this authentication is ready. ??
If you don't want to code everything yourself and want a quick solution, I have created an npm package called auth0_package. You can get it from here.
You can get my all above code here in this github repo here.
Now your backend application is complete. In the next blog, I will explain how to integrate this with your frontend. So stay tuned for that ??.
In conclusion, implementing secure authentication in a Node.js application is crucial for protecting user data and ensuring that only authorized users can access specific parts of your application. By using bcrypt.js for password hashing and JWT tokens for session management, you can create a robust login/logout system. This approach not only enhances security but also provides a seamless user experience. Setting up a MongoDB database and using Express for routing further strengthens the backend infrastructure. With these tools and techniques, you can confidently launch your web application, knowing that it is well-protected against unauthorized access and potential security threats.
The above is the detailed content of Mastering Secure Authentication in Node.js: Login/Logout with bcrypt.js and JWT. For more information, please follow other related articles on the PHP Chinese website!