Paramclasses Academy - Blog

Tutorials Details

←
Post 27 of 40
→

Authentication in Node.js: Login, Signup and Password Hashing

Authentication in Node.js: Login, Signup and Password Hashing

October 30, 2026

✦ Authentication in Node.js: Login, Signup and Password Hashing

❖ Introduction

Authentication is one of the most important features of modern web applications. It allows users to create accounts, log in, and access their personal information securely.

For example, a student management system may allow students to register and log in to view their profiles. Similarly, an e-commerce website allows customers to create accounts, log in, and manage their orders.

Node.js and Express.js make it possible to build authentication systems using backend APIs. However, storing passwords directly in a database is dangerous. Developers should never save plain-text passwords.

Instead, applications should use password hashing techniques to protect user credentials.

In this tutorial, you will learn how to:

  • Create a user registration API.

  • Create a login API.

  • Hash passwords using bcrypt.

  • Verify passwords during login.

  • Store user information in MySQL.

  • Handle validation and authentication errors.

✦ 1. What Is Authentication?

Authentication is the process of verifying the identity of a user.

For example, when someone logs into a website, they enter their email address and password. The backend checks whether the account exists and whether the submitted password matches the stored password hash.

A typical authentication process looks like this:

  1. User opens the registration page.

  2. User enters name, email, and password.

  3. Backend validates the submitted information.

  4. Backend hashes the password.

  5. User details are stored in the database.

  6. User enters email and password on the login page.

  7. Backend verifies the password.

  8. Login succeeds if the credentials are valid.

Authentication answers the question: Who are you?

Authorization is different. It determines what an authenticated user is allowed to access.

✦ 2. What Is Password Hashing?

Password hashing converts a password into a one-way cryptographic hash.

For example, suppose a user enters:

MyPassword123

Instead of storing this original password, the application stores a bcrypt hash.

A simplified example:

Original Password:
MyPassword123

Stored Password:
$2b$12$.............................................

The stored hash is not intended to be converted back into the original password.

During login, bcrypt compares the submitted password with the stored hash.

❖ Why Is Password Hashing Important?

  • Protects passwords if a database is exposed.

  • Prevents applications from storing plain-text credentials.

  • Uses a salt to make identical passwords produce different hashes.

  • Makes password guessing more expensive through configurable work factors.

Popular password-hashing options include:

  • bcrypt

  • Argon2id

  • scrypt

For this beginner tutorial, we will use bcrypt.

✦ 3. Create a Node.js Project

Open your terminal:

mkdir node-auth-project
cd node-auth-project
npm init -y

Install the required packages:

npm install express mysql2 bcrypt dotenv

Package explanations:

Package Purpose
express Creates backend API routes
mysql2 Connects Node.js to MySQL
bcrypt Hashes and verifies passwords
dotenv Loads environment variables

Create this project structure:

node-auth-project/
├── server.js
├── .env
├── .gitignore
└── package.json

✦ 4. Create the MySQL Database

Open phpMyAdmin or MySQL Workbench and execute:

CREATE DATABASE auth_db;

USE auth_db;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

The table contains:

  • id: Unique user ID.

  • name: User's full name.

  • email: User's unique email address.

  • password: Stored bcrypt password hash.

  • created_at: Account creation date and time.

The password column stores the hash, not the original password.

✦ 5. Configure the Database Connection

Create a .env file:

PORT=3000

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=
DB_NAME=auth_db
DB_PORT=3306

These are example local development settings. Change them according to your MySQL installation.

Create a .gitignore file:

node_modules/
.env

Never upload real database passwords or other secrets to a public Git repository.

✦ 6. Create the Signup and Login APIs

Open server.js and add the following complete code:

require("dotenv").config();

const express = require("express");
const mysql = require("mysql2/promise");
const bcrypt = require("bcrypt");

const app = express();

app.use(express.json());

// MySQL connection pool
const db = mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    port: Number(process.env.DB_PORT || 3306),
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
});

// Home route
app.get("/", (req, res) => {
    res.json({
        success: true,
        message: "Node.js Authentication API is running"
    });
});

// SIGNUP API
app.post("/signup", async (req, res) => {

    const { name, email, password } = req.body;

    // Basic validation
    if (
        typeof name !== "string" ||
        typeof email !== "string" ||
        typeof password !== "string" ||
        !name.trim() ||
        !email.trim() ||
        !password
    ) {
        return res.status(400).json({
            success: false,
            message: "Name, email and password are required"
        });
    }

    if (
        name.trim().length > 100 ||
        email.trim().length > 150 ||
        password.length < 12 ||
        password.length > 72
    ) {
        return res.status(400).json({
            success: false,
            message: "Invalid input or password length"
        });
    }

    try {

        // Check whether email already exists
        const [existingUsers] = await db.execute(
            "SELECT id FROM users WHERE email = ?",
            [email.trim().toLowerCase()]
        );

        if (existingUsers.length > 0) {
            return res.status(409).json({
                success: false,
                message: "Email already registered"
            });
        }

        // Hash password
        const saltRounds = 12;

        const hashedPassword = await bcrypt.hash(
            password,
            saltRounds
        );

        // Insert user into database
        const [result] = await db.execute(
            `INSERT INTO users (name, email, password)
             VALUES (?, ?, ?)`,
            [
                name.trim(),
                email.trim().toLowerCase(),
                hashedPassword
            ]
        );

        res.status(201).json({
            success: true,
            message: "Registration successful",
            userId: result.insertId
        });

    } catch (error) {

        // Handle duplicate email safely
        if (error.code === "ER_DUP_ENTRY") {
            return res.status(409).json({
                success: false,
                message: "Email already registered"
            });
        }

        console.error("Signup error:", error.message);

        res.status(500).json({
            success: false,
            message: "Registration failed"
        });
    }
});

// LOGIN API
app.post("/login", async (req, res) => {

    const { email, password } = req.body;

    if (
        typeof email !== "string" ||
        typeof password !== "string" ||
        !email.trim() ||
        !password
    ) {
        return res.status(400).json({
            success: false,
            message: "Email and password are required"
        });
    }

    try {

        // Find user by email
        const [users] = await db.execute(
            "SELECT id, name, email, password FROM users WHERE email = ?",
            [email.trim().toLowerCase()]
        );

        if (users.length === 0) {
            return res.status(401).json({
                success: false,
                message: "Invalid email or password"
            });
        }

        const user = users[0];

        // Compare submitted password with stored hash
        const passwordMatch = await bcrypt.compare(
            password,
            user.password
        );

        if (!passwordMatch) {
            return res.status(401).json({
                success: false,
                message: "Invalid email or password"
            });
        }

        // Successful login
        res.json({
            success: true,
            message: "Login successful",
            user: {
                id: user.id,
                name: user.name,
                email: user.email
            }
        });

    } catch (error) {

        console.error("Login error:", error.message);

        res.status(500).json({
            success: false,
            message: "Login failed"
        });
    }
});

// Start server after database check
async function startServer() {
    try {
        await db.query("SELECT 1");

        const PORT = Number(process.env.PORT || 3000);

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

    } catch (error) {
        console.error("Database connection failed:", error.message);
        process.exitCode = 1;
    }
}

startServer();

This is a learning example of signup and password verification. A production authentication system should also include stronger request validation, rate limiting, account recovery, secure session or token management, and protection against automated login attempts.

Important bcrypt detail: bcrypt has a 72-byte password input limit. This example limits the password length to 72 characters, but Unicode characters can use multiple bytes. Production validation should enforce the byte limit explicitly or use an alternative such as Argon2id.

✦ 7. Understand the Signup Process

The signup route receives:

{
    "name": "Aman Verma",
    "email": "aman@example.com",
    "password": "MySecurePassword123"
}

First, the application checks whether the required fields are present.

Next, it checks whether the email is already registered.

Then, bcrypt hashes the password:

const hashedPassword = await bcrypt.hash(
    password,
    12
);

Finally, the application saves the user information:

const [result] = await db.execute(
    "INSERT INTO users (name, email, password) VALUES (?, ?, ?)",
    [name, email, hashedPassword]
);

The database receives the hashed password instead of the original password.

✦ 8. Understand the Login Process

When a user logs in, the application receives:

{
    "email": "aman@example.com",
    "password": "MySecurePassword123"
}

The backend searches for the user:

const [users] = await db.execute(
    "SELECT * FROM users WHERE email = ?",
    [email]
);

It then verifies the password:

const passwordMatch = await bcrypt.compare(
    password,
    user.password
);

If the password matches, login succeeds.

If it does not match, the API returns an authentication error.

Remember: Never compare a submitted password directly with a stored bcrypt hash using ===. Use bcrypt.compare().

✦ 9. Run the Application

Start your Node.js server:

node server.js

If everything is configured correctly, the terminal displays:

Server running at http://localhost:3000

You can now test the signup and login APIs using Postman.

✦ 10. Test Signup API Using Postman

Select:

  • Method: POST

  • URL: http://localhost:3000/signup

  • Body: raw

  • Format: JSON

Enter:

{
    "name": "Aman Verma",
    "email": "aman@example.com",
    "password": "MySecurePassword123"
}

Example successful response:

{
    "success": true,
    "message": "Registration successful",
    "userId": 1
}

The generated user ID may be different depending on existing database records.

To verify that the password is hashed, execute:

USE auth_db;

SELECT id, name, email, password FROM users;

You should see a bcrypt hash in the password column, not the original password.

✦ 11. Test Login API Using Postman

Select:

  • Method: POST

  • URL: http://localhost:3000/login

  • Body: raw

  • Format: JSON

Enter:

{
    "email": "aman@example.com",
    "password": "MySecurePassword123"
}

Example successful response:

{
    "success": true,
    "message": "Login successful",
    "user": {
        "id": 1,
        "name": "Aman Verma",
        "email": "aman@example.com"
    }
}

If the password is incorrect, the API returns:

{
    "success": false,
    "message": "Invalid email or password"
}

The login response does not include the stored password hash.

✦ 12. Authentication Security Best Practices

Authentication involves sensitive user information. Follow these practices when building a real application:

❖ Never Store Plain-Text Passwords

Always use a password-hashing algorithm such as Argon2id or bcrypt.

❖ Use HTTPS

HTTPS protects credentials while they travel between the browser and server.

❖ Add Rate Limiting

Limit repeated login attempts to reduce automated password guessing.

❖ Use Secure Authentication Sessions

After successful login, use a properly configured server-side session or a secure token-based system. Do not treat a successful login response alone as proof of authentication on later requests.

❖ Validate User Input

Check email formats, password requirements, and field lengths on the backend.

❖ Avoid Revealing Account Existence

Use generic login error messages such as "Invalid email or password."

❖ Protect Database Credentials

Store credentials in environment variables or a secrets manager.

❖ Add Account Recovery

Build a secure password-reset process using expiring, single-use reset tokens.

❖ Never Log Passwords

Do not write passwords, password hashes, or authentication secrets into application logs.

✦ Frequently Asked Questions (FAQs)

❖ 1. What is authentication in Node.js?

Authentication in Node.js is the process of verifying user identities through backend logic such as registration, login, password verification, and session or token management.

❖ 2. Why should passwords be hashed?

Hashing helps protect user passwords if a database is exposed. Applications should store password hashes rather than plain-text passwords.

❖ 3. What is bcrypt in Node.js?

Bcrypt is a password-hashing library. It generates salted password hashes and provides a comparison method to verify submitted passwords.

❖ 4. What is the difference between authentication and authorization?

Authentication verifies who the user is. Authorization determines which resources and actions the user is allowed to access.

❖ 5. Is password hashing enough to secure login?

No. A secure authentication system also needs HTTPS, rate limiting, input validation, secure session or token management, and account recovery protections.

❖ 6. Can I use MongoDB instead of MySQL for authentication?

Yes. Node.js authentication can use MongoDB with Mongoose or the official MongoDB driver. The password-hashing principles remain the same.

✦ Conclusion

Authentication is a fundamental part of backend development. In this tutorial, you learned how to create signup and login APIs using Node.js, Express.js, MySQL, and bcrypt.

You also learned why password hashing is important, how to store password hashes, and how to verify credentials during login.

These concepts are useful when developing student management systems, e-commerce websites, CRM applications, admin dashboards, and other database-driven platforms.

To learn practical programming and backend development, visit Param Classes. For website development and digital solutions, visit ParamWebInfo.

←
Post 27 of 40
→