Lesson 1
User Authentication
Introduction

Welcome to our lesson on User Authentication! Today, we'll learn how to add user authentication to our To-Do List application using Express.js and MongoDB. This is important because it ensures that only registered users can access their personalized task lists, making our app more secure and user-friendly.

What You'll Learn

In this lesson, you'll learn:

  • What user authentication is and why it's important.
  • How to create a user model in MongoDB.
  • How to handle user registration and login using Express.js.
  • How to hash passwords to enhance security.

Now that we know what we're about to learn, let's understand user authentication in more detail.

Introduction to User Authentication

User authentication is the process of verifying the identity of a user when they access an application. It's like checking someone's ID before allowing them to enter a building.

Imagine your To-Do List app is like a personal diary. You want to make sure that only you can see and add tasks to it. That's where user authentication comes in — it makes sure that only registered users with the correct credentials can access their data.

Step 1: Setting Up the Environment

First, let's set up our environment to handle user authentication. We need to:

  1. Install necessary libraries (Express.js, MongoDB, bcrypt).
  2. Connect to the MongoDB database.
  3. Set up an Express.js server.

Ensure you have Node.js and MongoDB installed on your machine.

To install the necessary libraries, run the following commands:

Bash
1npm install express mongoose bcrypt

Here's the code to set up the environment:

JavaScript
1const express = require('express'); 2const mongoose = require('mongoose'); 3const bcrypt = require('bcrypt'); 4 5const app = express(); 6const PORT = 3000; 7 8// Connect to MongoDB 9mongoose.connect('mongodb://127.0.0.1:27017/todo-app', { 10 useNewUrlParser: true, 11 useUnifiedTopology: true 12}).catch(error => console.log('Error connecting to MongoDB:', error)); 13 14app.use(express.json()); // Replace bodyParser with express built-in middleware 15 16app.listen(PORT, () => { 17 console.log(`Server running on http://localhost:${PORT}`); 18});

Potential error: If there is an issue with the database connection, an error message will be logged.

Step 2: Creating a User Model

Next, we’ll create a User model in MongoDB. This model will define the structure of our user data.

JavaScript
1// Define a schema and model for Users 2const userSchema = new mongoose.Schema({ 3 username: { type: String, required: true, unique: true }, 4 password: { type: String, required: true } 5}); 6 7const User = mongoose.model('User', userSchema);

Here, we define a userSchema with username and password fields. Both fields are required, and username must be unique. This ensures that each user has a unique identifier and a secure password.

Step 3: Registering a New User

Now, let's add functionality to register new users. We’ll create an endpoint /register that will handle new user registrations. To keep our passwords secure, we'll hash them using bcrypt before saving them to the database.

Hashing is a process of converting a password into a fixed-length string of characters, which is typically a hash code. It’s important because it enhances security by not storing passwords in plaintext. Hashing functions like bcrypt.hash(password, saltRounds) take two arguments: the plain-text password and a number of salt rounds (cost factor). For example, bcrypt.hash('mypassword', 10) hashes the password 'mypassword' with 10 salt rounds, producing a secure hashed output that cannot be easily reversed.

For instance, the password 'mypassword' might be hashed to something like $2b$10$CwTycUXWue0Thq9StjUM0uJ8wz6v5FUV5gvTr6GMoXw8s0/Ov9Vi..

It's crucial to protect passwords and avoid saving them in non-hashed form because plaintext passwords can be easily stolen and misused in case of a data breach.

We use async/await to handle asynchronous operations like password hashing and saving the user to the database. This helps write cleaner and more readable code.

Here's how to use bcrypt for hashing:

JavaScript
1const bcrypt = require('bcrypt'); 2 3// Route to handle user registration 4app.post('/register', async (req, res) => { 5 const { username, password } = req.body; 6 7 try { 8 // Hash the password before saving it to the database 9 const hashedPassword = await bcrypt.hash(password, 10); 10 11 const newUser = new User({ username, password: hashedPassword }); 12 const savedUser = await newUser.save(); 13 res.status(201).json(savedUser); 14 } catch (error) { 15 res.status(400).json({ message: 'Failed to register user', error: error.message }); 16 } 17});

In this code, we hash the user's password using bcrypt before storing it in the database. We then create a new User object and attempt to save it to the database. If it succeeds, we return the registered user; otherwise, we return an error message.

Step 4: Logging In a User

Finally, we’ll add functionality for users to log in. We'll create an endpoint /login that will check if the provided credentials are correct.

When a user tries to log in, we need to:

  1. Find the user by their username.
  2. Compare the provided password with the stored hashed password.

Here’s the code for the login endpoint:

JavaScript
1// Route to handle user login 2app.post('/login', async (req, res) => { 3 const { username, password } = req.body; 4 5 try { 6 const user = await User.findOne({ username }); 7 if (!user) return res.status(401).json({ message: 'Invalid credentials' }); 8 9 // Compare the hashed password with the stored hashed password 10 const isPasswordCorrect = await bcrypt.compare(password, user.password); 11 if (!isPasswordCorrect) return res.status(401).json({ message: 'Invalid credentials' }); 12 13 res.json({ message: 'Login successful' }); 14 } catch (error) { 15 // The try-catch block is used for handling potential errors that might occur during login. 16 res.status(500).json({ message: 'Failed to log in', error: error.message }); 17 } 18});

In this code, we look for the user in our database by their username. If the user exists, we compare the provided password with the stored hashed password using bcrypt. If they match, the user is successfully logged in; otherwise, we return an error message.

Please note that the try-catch block is specifically used to handle potential errors that might occur during the database query or password comparison, such as when the database connection is lost or the bcrypt.compare function fails.

Conclusion

In this lesson, we learned what user authentication is and why it’s important. We set up our environment, created a user model, and added functionality for user registration and login. We also learned about password hashing using bcrypt to enhance security.

Get ready to dive into the exercises and build your skills!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.