In this blog, we explore an updated version of a simple user authentication system using Express.js, PostgreSQL, and bcrypt for password encryption. This version addresses security concerns by using password hashing and comparison, ensuring that sensitive data is not stored in plain text.
1. Encryption
Definition:
Encryption is the process of converting plaintext (readable data) into ciphertext (unreadable data) using a mathematical algorithm and a key. The encrypted data can be decrypted back to its original form using a key.
Key Features:
Reversible: Encrypted data can be decrypted back to plaintext if you have the correct key.
Uses: Protects sensitive data during transmission or storage.
Algorithms: AES (Advanced Encryption Standard), RSA, DES, etc.
Types of Encryption:
Symmetric Encryption:
A single key is used for both encryption and decryption.
Fast and efficient but requires secure key sharing.
Example: AES.
Asymmetric Encryption:
Uses a pair of keys: a public key for encryption and a private key for decryption.
More secure but slower.
Example: RSA.
Applications:
Securing communication (e.g., HTTPS, email).
Protecting files and databases.
2. Hashing
Definition:
Hashing is the process of converting any data into a fixed-length string (hash value) using a mathematical function. It is a one-way process, meaning you cannot reverse a hash to retrieve the original data.
Key Features:
Irreversible: Once data is hashed, it cannot be converted back to its original form.
Fixed-Length Output: Regardless of input size, the hash function always produces a fixed-size output (e.g., SHA-256 outputs 256 bits).
Deterministic: The same input will always produce the same hash.
Collision Resistance: Two different inputs should not produce the same hash.
Common Hashing Algorithms:
MD5: Fast but insecure due to vulnerabilities.
SHA (Secure Hash Algorithm): Includes SHA-1, SHA-256, and SHA-3. SHA-256 is widely used for security purposes.
Bcrypt: A hashing algorithm specifically designed for password hashing, which includes salting and adaptive difficulty.
Applications:
Password storage: Instead of storing plaintext passwords, store hashes.
Verifying file integrity: Ensuring data has not been tampered with (e.g., checksums).
Blockchain: Hashing is crucial for securing transactions.
3. Salting
Definition:
Salting is the process of adding a unique random value (salt) to the input before hashing it. This makes the hash output unique even for identical inputs.
Key Features:
Prevents Rainbow Table Attacks: A rainbow table is a precomputed table of hash values. Salting ensures that hashes for the same password are different, making these tables ineffective.
Unique per User: Each user’s password should be hashed with a unique salt.
Example of Hashing with Salting:
User inputs password:
mypassword
.Random salt is generated:
xyz123
.Combine password and salt:
mypasswordxyz123
.Hash the result:
SHA256(mypasswordxyz123)
→ Produces a unique hash.
Applications:
Password storage in databases.
Enhancing hash security in sensitive applications.
Key Features:
Registration: Hashes the password before saving it to the database.
Login: Compares the entered password with the stored hashed password for verification.
Database: Uses PostgreSQL to store user credentials securely.
Let’s break down the code and explain how each part contributes to user authentication with better security.
1. Setup and Dependencies
import express from "express";
import bodyParser from "body-parser";
import pg from "pg";
import bcrypt from "bcrypt";
We import the necessary modules:
express: A web framework for Node.js that simplifies routing.
bodyParser: To parse incoming request bodies.
pg: A PostgreSQL client to interact with the database.
bcrypt: A library to hash and compare passwords securely.
2. Express App and Middleware Setup
const app = express();
const port = 3000;
const saltRounds = 10;
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
We create an Express application instance (
app
).saltRounds: Defines the cost factor for bcrypt's hashing algorithm. Higher values increase security but also computational cost.
bodyParser middleware is used to parse URL-encoded data (for form submissions).
express.static serves static files like CSS and JavaScript from the
public
folder.
3. Database Connection
const db = new pg.Client({
user: "postgres",
host: "localhost",
database: "secrets",
password: "mspostgres",
port: 5432,
});
db.connect();
Here, we configure the connection to the PostgreSQL database, specifying the user, host, database name, password, and port. This allows the app to interact with the database and store/retrieve user data.
4. Routes for Home, Login, and Registration Pages
app.get("/", (req, res) => {
res.render("home.ejs");
});
app.get("/login", (req, res) => {
res.render("login.ejs");
});
app.get("/register", (req, res) => {
res.render("register.ejs");
});
- GET routes render the homepage (
home.ejs
), login page (login.ejs
), and registration page (register.ejs
), allowing users to navigate between the pages.
5. Registration Route (Password Hashing)
app.post("/register", async (req, res) => {
const email = req.body.username;
const password = req.body.password;
try {
const checkResult = await db.query("SELECT * FROM users WHERE email = $1", [
email,
]);
if (checkResult.rows.length > 0) {
res.send("Email already exists. Try logging in.");
} else {
// Hashing the password and saving it in the database
bcrypt.hash(password, saltRounds, async (err, hash) => {
if (err) {
console.error("Error hashing password:", err);
} else {
console.log("Hashed Password:", hash);
await db.query(
"INSERT INTO users (email, password) VALUES ($1, $2)",
[email, hash]
);
res.render("login.ejs");
}
});
}
} catch (err) {
console.log(err);
}
});
Password Hashing: When a user registers, the password they provide is hashed using bcrypt.hash() with a salt factor of
10
(saltRounds
).If the email already exists in the database, an error message is displayed. Otherwise, the hashed password is saved to the database.
Error Handling: We handle errors in hashing and database queries to ensure smooth execution.
6. Login Route (Password Comparison)
app.post("/login", async (req, res) => {
const email = req.body.username;
const loginPassword = req.body.password;
try {
const result = await db.query("SELECT * FROM users WHERE email = $1", [
email,
]);
if (result.rows.length > 0) {
const user = result.rows[0];
const storedHashedPassword = user.password;
// Verifying the password
bcrypt.compare(loginPassword, storedHashedPassword, (err, result) => {
if (err) {
console.error("Error comparing passwords:", err);
} else {
if (result) {
res.render("secrets.ejs");
} else {
res.send("Incorrect Password");
}
}
});
} else {
res.send("User not found");
}
} catch (err) {
console.log(err);
}
});
Password Comparison: When a user attempts to log in, the entered password (
loginPassword
) is compared to the stored hashed password using bcrypt.compare().If the comparison returns
true
, the user is granted access and redirected to the secrets page (secrets.ejs
). Otherwise, an error message is shown.
7. Starting the Server
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
- The Express server starts listening on the specified port (
3000
), and a confirmation message is logged to the console.
Security Benefits of Hashing Passwords:
Protection Against Data Breaches: Even if the database is compromised, hashed passwords are nearly impossible to reverse into the original password, especially with a strong salt factor.
Preventing Plaintext Passwords: Storing passwords in plaintext is highly insecure. By hashing passwords, you prevent this vulnerability.
Salted Hashes: Each password is hashed with a unique salt, making it difficult to use precomputed tables (rainbow tables) to crack passwords.
Conclusion:
This "User Authentication Level 2" system builds upon the previous version by securely hashing passwords using bcrypt before storing them in the database. It also ensures that the login system compares the entered password against the hashed version stored in the database for verification.
Encryption is ideal for protecting data that needs to be decrypted later.
Hashing is essential for storing sensitive data like passwords in an irreversible format.
Salting enhances hashing by adding randomness, protecting against precomputed attacks like rainbow tables.
This enhanced approach makes the application more secure and ready for production environments where user data must be protected.