Setting Up Passport.js for User Authentication

Lesson 3

Welcome to Lesson 3 of the "Unlock The Blockchain" tutorial series! This chapter will guide you through implementing Passport.js for user authentication in our Crypto Portfolio Tracker app. Authentication is pivotal in web development, ensuring only authorized users can access specific functionalities. By using Passport.js along with bcrypt for password handling, we aim to establish a secure, flexible authentication system to protect user data and bolster our app's security.

Why Passport.js and bcrypt?

  • Passport.js is versatile authentication middleware for Node.js. It offers straightforward integration for various authentication methods, including username and password, OAuth, and more.

  • bcrypt is a trusted library for password hashing, making it a staple for securely storing user passwords.

Introducing Passport.js and bcrypt:

Passport.js streamlines adding authentication to Node.js apps with a middleware that handles tasks like verifying credentials and managing sessions. bcrypt provides a secure way to hash passwords, which is crucial for safeguarding user credentials in the database.

Key Concepts:

Before diving into the setup, let's familiarize ourselves with some essential concepts:

  • Local Authentication: We'll implement local authentication, allowing users to sign up, log in, and log out using their email and password.

  • Password Hashing: To ensure passwords are stored securely, we'll use bcrypt to hash passwords before they're saved in the database.

  • Session Management: Passport.js works well with session middleware, like express-session, to manage user sessions across HTTP requests.

Integrating Passport.js and bcrypt:

Follow these high-level steps to incorporate user authentication into our app:

npm install passport passport-local bcrypt express-session

npm install --save-dev @types/passport @types/passport-local @types/bcrypt @types/express-session

Configure Passport Local Strategy:

In passportConfig.ts set up the local strategy for authentication:

import { PassportStatic } from "passport";
import { Strategy as LocalStrategy, VerifyFunction } from "passport-local";
import bcrypt from "bcrypt";
import User from "../models/User";

// Use the LocalStrategy within Passport.
// Strategies in passport require a `verify` function, which accept credentials,
// and invoke a callback with a user object.
const verify: VerifyFunction = async (username, password, done) => {
  // Match user based on username
  try {
    const user = await User.findOne({ username });
    if (!user) return done(null, false);
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) return done(null, false);
    return done(null, user);
  } catch (error) {
    return done(error as Error);
  }
};

function configurePassport(passport: PassportStatic) {
  passport.use(new LocalStrategy(verify));

  // Used to serialize the user for the session
  passport.serializeUser((user, done) => {
    done(null, user.id);
  });

  // Used to deserialize the user
  passport.deserializeUser((id, done) => {
    User.findById(id)
      .then((user) => done(null, user))
      .catch((error) => done(error));
  });
}

export default configurePassport;

Setup Authentication Routes:

Define routes in authRoutes.ts for registration, login, and logout.

import express from "express";
import passport from "passport";
import bcrypt from "bcrypt";
import User from "../models/User";

const router = express.Router();

// Route for user registration
router.post("/register", async (request, response) => {
  const { username, password } = request.body;

  // Check if username and password are provided
  if (!username || !password) {
    return response
      .status(400)
      .json({ message: "Please provide both username and password" });
  }

  // Hash password before saving to database
  const hashedPassword = await bcrypt.hash(password, 10);
  // Create a new user
  try {
    const newUser = await User.create({ username, password: hashedPassword });
    return response
      .status(201)
      .json({ message: "User created successfully", user: newUser });
  } catch (error) {
    return response.status(500).json({ message: "Error creating user", error });
  }
});

// Route for user login
// Use passport.authenticate middleware to authenticate user
router.post(
  "/login",
  passport.authenticate("local", {
    successRedirect: "/dashboard", // Redirect to dashboard if login is successful
    failureRedirect: "/login", // Redirect to login page if login fails
  })
);

// Route for user logout
router.get("/logout", (request, response) => {
  request.logOut((error) => {
    if (error) {
      console.error("Logout failed due to error:", error);
    }
  }); // Passport logout method
  response.redirect("/"); // Redirect to home page after logout
});

export default router;

Integrate Passport into Express Application:

Configure Passport and sessions in server.ts:

import express from "express";
import session from "express-session";
import mongoose from "mongoose";
import dotenv from "dotenv";
import passport from "passport";
import configurePassport from "./config/passportConfig";
import authRoutes from "./routes/authRoutes";

dotenv.config();

const app = express();
const port = 3000;

mongoose
  .connect(process.env.MONGODB_URI as string)
  .then(() => console.log("Connected to MongoDB"))
  .catch((err) => console.log("MongoDB connection error", err));

app.use(express.json()); // Parse JSON request body

app.use(
  session({
    secret: process.env.SESSION_SECRET as string,
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false },
  })
); // Session middleware

app.use(passport.initialize());
app.use(passport.session());
configurePassport(passport);

app.get("/", (_req, res) => {
  res.send("Login or register");
});

app.get("/dashboard", (req, res) => {
  if (req.isAuthenticated()) {
    res.send("Dashboard");
  } else {
    res.redirect("/login");
  }
});

// Use authRoutes
app.use("/auth", authRoutes);

app.listen(port, () => {
  console.log(`Server is running at http://localhost:${port}`);
});

Testing:

Ensure that user registration, login, and logout functionalities work as intended.

Conclusion:

With Passport.js and bcrypt, you've added a comprehensive user authentication system to your application, enhancing its security and functionality. This solid foundation supports further features, like wallet management and transaction tracking, in a secure environment.

Looking Ahead:

As we progress, consider exploring more advanced authentication strategies and additional security features, such as password recovery and account verification, to make our app more robust and user-friendly. The journey to mastering blockchain development continues.

Last updated