How to Secure Your Database in 5 Steps
Do you have a question or doubt about something?
Scroll down to the bottom to ask your question, and I or anyone else will respond!
According to recent security analysis, the #1 threat to databases in 2026 isn't SQL injection - it's credential stuffing .
Step 1: Stop Credential Stuffing at the Login Form
Credential stuffing occurs when attackers take leaked credentials from one breach and try them on your site. Attackers use AI to automate this process at massive scale .
The solution:
-- Add these columns to your users table
ALTER TABLE users ADD COLUMN login_attempts INT DEFAULT 0;
ALTER TABLE users ADD COLUMN last_login_attempt TIMESTAMP;
ALTER TABLE users ADD COLUMN locked_until TIMESTAMP;
// Backend rate limiting logic
async function handleLogin(email, password) {
const user = await db.user.findUnique({ where: { email } });
// Check if account is locked
if (user.lockedUntil && user.lockedUntil > new Date()) {
return { error: 'Account locked. Try again later.' };
}
// Check rate limit (max 5 attempts per 15 minutes)
if (user.loginAttempts >= 5 &&
user.lastLoginAttempt > new Date(Date.now() - 15 * 60 * 1000)) {
await db.user.update({
where: { email },
data: { lockedUntil: new Date(Date.now() + 30 * 60 * 1000) }
});
return { error: 'Too many attempts. Account locked for 30 minutes.' };
}
// Verify password...
}
Add 2FA for critical accounts - it's the single most effective security measure you can implement .
Step 2: Implement Proper Password Hashing
Never store plain text passwords. Never use MD5 or SHA1.
// ✅ CORRECT: Using bcrypt
import bcrypt from 'bcrypt';
const saltRounds = 12; // OWASP recommends 10-12
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Verification
const isValid = await bcrypt.compare(inputPassword, storedHash);
❌ Wrong:
// NEVER DO THIS
const hash = crypto.createHash('md5').update(password).digest('hex');
const hash = crypto.createHash('sha1').update(password).digest('hex');
Step 3: Apply the Principle of Least Privilege
Your application shouldn't be able to delete the entire database.
-- Create limited-purpose database users
-- Read-only user for public content
CREATE USER 'app_readonly'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT ON mydb.* TO 'app_readonly'@'localhost';
-- Write user for transactions (no schema changes)
CREATE USER 'app_readwrite'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_readwrite'@'localhost';
-- Admin user for migrations (used separately)
CREATE USER 'app_admin'@'localhost' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON mydb.* TO 'app_admin'@'localhost';
Your production app runs as app_readwrite. Only migrations run as admin.
Step 4: Enable Comprehensive Logging and Monitoring
Attackers prefer small, frequent queries that blend in with normal traffic .
What to log:
-- Enable query logging (MySQL/MariaDB)
SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';
-- Create monitoring query to detect anomalies
SELECT * FROM mysql.general_log
WHERE command_type = 'Query'
AND (argument LIKE 'DROP%'
OR argument LIKE 'TRUNCATE%'
OR argument LIKE 'ALTER%')
ORDER BY event_time DESC;
Weekly routine:
- Export logs from the past 7 days
- Search for unusual patterns (massive exports, odd query times)
- Check for failed login spikes
- Review privilege changes
> "You'd be surprised how many records you'll acquire" - regular log inspection catches breaches early .
Step 5: Encrypt Sensitive Data
Not all data needs the same protection level.
| Data Type | Recommended Protection |
|---|---|
| Passwords | bcrypt (12 rounds) |
| PII (names, emails) | AES-256 encryption at rest |
| Payment info | Tokenization (Stripe, not your database) |
| Session tokens | Signed JWT + short expiration |
| API keys | Hash (bcrypt) + store with scopes |
For column-level encryption (PostgreSQL):
-- Enable pgcrypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Encrypt sensitive columns
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT,
encrypted_ssn BYTEA DEFAULT pgp_sym_encrypt(ssn, 'my-secret-key')
);
Professional Security Checklist
Before deployment, verify:
- Rate limiting on all authentication endpoints
- 2FA available for sensitive operations
- Passwords hashed with bcrypt (or PBKDF2/Argon2)
- Database users have minimal necessary privileges
- Logging enabled and you review it weekly
- Backups are encrypted and stored separately
- Prepared statement ORM (no raw SQL concatenation)
- Regular security updates for database server
Resources
| Topic | Resource |
|---|---|
| OWASP Top 10 | owasp.org/Top10 |
| bcrypt best practices | auth0.com/blog/hashing-passwords |
| PostgreSQL security | postgresql.org/docs/current/security.html |
| Credential stuffing prevention | owasp.org/credential-stuffing |
Discussion
0Do you have a question or any doubt?
Ask here and I or anyone else will respond!
By 2BigDev
Full-Stack Engineer