If you're storing passwords in plain text in MySQL, we need to have an intervention. It's 2025. We have drones delivering tacos, but some devs treat passwords like grocery lists.
Opening a database and seeing "password123" just chilling there unprotected? That's leaving your door open with a neon "FREE STUFF" sign.
The Horror You're Creating
You just built your first Node.js auth system. Feeling brilliant. Then you write THIS:
app.post('/register', (req, res) => {
const { username, password } = req.body;
db.query('INSERT INTO users (username, password) VALUES (?, ?)',
[username, password]); // 🚨 DISASTER
});
Congrats! When your database leaks (when, not if), every password sits there like free samples at Costco.
People reuse passwords everywhere. That "fluffy2023"? You just gave hackers access to Sarah's email, banking, and Instagram. Her cat Fluffy is judging you.
Solution: BCrypt Saves Everything
BCrypt blends passwords beyond recognition with no "undo" button. Hackers see gibberish like $2b$10$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW.
const bcrypt = require('bcrypt');
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
db.query('INSERT INTO users (username, password) VALUES (?, ?)',
[username, hashedPassword]);
});
The 10 is "salt rounds"—how many scramble cycles. Ten is perfect for 2025.
Mistake #2: Comparing Wrong
You hashed passwords? Don't ruin it with this:
// WRONG - Never works
if (results[0].password === password) {
res.send('Logged in!');
}
You can't compare hashed with plain text using ===. That's asking if a smoothie equals an apple.
The fix:
app.post('/login', async (req, res) => {
const { username, password } = req.body;
db.query('SELECT * FROM users WHERE username = ?', [username],
async (err, results) => {
const match = await bcrypt.compare(password, results[0].password);
if (match) {
res.send('Welcome!');
} else {
res.send('Try again');
}
});
});
BCrypt's compare() handles verification magically (it's math, but feels like magic).
Mistake #3: No Validation
Accepting ANY password is suicide. "1"? Welcome! Empty string? Sure!
function isPasswordValid(password) {
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
return regex.test(password);
}
app.post('/register', async (req, res) => {
if (!isPasswordValid(req.body.password)) {
return res.status(400).send('Need 8+ chars, letters, numbers & symbols');
}
// continue with hashing...
});
Mistake #4: SQL Injection
Building queries with template strings? Asking for disaster:
// NEVER
const query = `SELECT * FROM users WHERE username = '${username}'`;
Type admin'-- and BAM—logged in as admin without a password.
Use parameterized queries ALWAYS:
db.query('SELECT * FROM users WHERE username = ?', [username]);
Those ? placeholders treat input as VALUES, not code.
Complete Secure Example
const express = require('express');
const bcrypt = require('bcrypt');
const mysql = require('mysql2/promise');
const app = express();
app.use(express.json());
function isPasswordValid(password) {
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
return regex.test(password);
}
app.post('/register', async (req, res) => {
const { username, password } = req.body;
if (!isPasswordValid(password)) {
return res.status(400).json({ error: 'Weak password!' });
}
const hashedPassword = await bcrypt.hash(password, 10);
await db.query('INSERT INTO users (username, password) VALUES (?, ?)',
[username, hashedPassword]);
res.json({ message: 'Account created!' });
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const [results] = await db.query('SELECT * FROM users WHERE username = ?',
[username]);
if (!results.length) {
return res.status(401).send('Invalid credentials');
}
const match = await bcrypt.compare(password, results[0].password);
if (match) {
res.json({ message: 'Login successful!' });
} else {
res.status(401).send('Invalid credentials');
}
});
Wake Up Call
It's 2025. AI writes code and cars drive themselves. Zero excuse for MySpace-era password storage.
Hash with BCrypt, validate inputs, use parameterized queries, never store plain text. Your users trust you—don't be tomorrow's breach headline.
Go secure those passwords before the internet finds you. And trust me, it will.
Top comments (2)
This should honestly be required reading for anyone building auth for the first time.
The “when, not if” database leak line hits hard — and the bcrypt examples are super clear.
Plain-text passwords still showing up in 2025 is scary, but posts like this help fix that. Great write-up 👍
Thanks so much, Bhavin! 🙌 Really happy to hear the article came across as clear and useful — especially the “when, not if” bit about database leaks. That’s exactly the mindset shift I was hoping to spark: secure password handling isn’t optional anymore. Appreciate the encouragement and glad you found the bcrypt examples approachable! 🚀