| name | auth-hardening |
| description | Harden authentication systems against session fixation, brute-force attacks, and weak password storage. Covers bcrypt/argon2 hashing, JWT best practices, session management, and account lockout. Use when implementing login, fixing auth vulnerabilities, or reviewing authentication code. |
| version | 1.0 |
Auth Hardening
Harden authentication to prevent unauthorized access. Weak auth is OWASP A07 and a direct path to PDPA liability -- if attackers access user data through weak login security, the developer is negligent.
The Rule
NEVER roll your own crypto. ALWAYS use proven libraries with secure defaults.
Password Hashing
The Only Acceptable Algorithms
| Algorithm | When to use | Config |
|---|
| Argon2id | New projects (recommended) | memory: 64MB, iterations: 3, parallelism: 4 |
| bcrypt | Existing projects, wide support | cost factor: 12+ |
| scrypt | Alternative to Argon2 | N=2^17, r=8, p=1 |
NEVER use: MD5, SHA-1, SHA-256 (without salt+stretch), plain text
Node.js
BAD:
import crypto from 'crypto';
const hash = crypto.createHash('sha256').update(password).digest('hex');
GOOD:
import { hash, verify } from '@node-rs/argon2';
const passwordHash = await hash(password, {
memoryCost: 65536,
timeCost: 3,
parallelism: 4,
});
const isValid = await verify(passwordHash, password);
Alternative with bcrypt:
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);
const isValid = await bcrypt.compare(password, passwordHash);
Python
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)
password_hash = ph.hash(password)
try:
ph.verify(password_hash, password)
except argon2.exceptions.VerifyMismatchError:
raise InvalidCredentials()
Laravel
$hash = Hash::make($password);
$valid = Hash::check($password, $hash);
'driver' => 'argon2id',
'memory' => 65536,
'threads' => 4,
'time' => 3,
JWT Best Practices
Token Configuration
import { SignJWT, jwtVerify } from 'jose';
const SECRET = new TextEncoder().encode(env.JWT_SECRET);
const token = await new SignJWT({ sub: userId, role: user.role })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m')
.setAudience('https://myapp.com')
.setIssuer('https://myapp.com')
.sign(SECRET);
const { payload } = await jwtVerify(token, SECRET, {
audience: 'https://myapp.com',
issuer: 'https://myapp.com',
algorithms: ['HS256'],
});
JWT Rules
- Access tokens: 15 minutes max
- Refresh tokens: 7 days max, stored in httpOnly cookie
- NEVER store JWTs in localStorage (XSS can steal them)
- ALWAYS validate
exp, aud, iss, and alg
- NEVER use
alg: "none" -- always specify allowed algorithms
- Rotate signing keys periodically
Brute-Force Protection
import { RateLimiterMemory } from 'rate-limiter-flexible';
const loginLimiter = new RateLimiterMemory({
points: 5,
duration: 900,
blockDuration: 900,
});
app.post('/login', async (req, res) => {
const key = `${req.ip}_${req.body.email}`;
try {
await loginLimiter.consume(key);
} catch {
return res.status(429).json({
error: 'Too many login attempts. Try again in 15 minutes.',
});
}
const user = await authenticate(req.body.email, req.body.password);
if (!user) {
return res.status(401).json({ error: 'Invalid email or password' });
}
await loginLimiter.delete(key);
return res.json({ token: generateToken(user) });
});
Session Management
const SESSION_OPTIONS = {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000,
path: '/',
domain: '.myapp.com',
};
app.post('/login', async (req, res) => {
const user = await authenticate(req.body);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
req.session.regenerate((err) => {
req.session.userId = user.id;
req.session.save();
res.json({ success: true });
});
});
app.post('/change-password', async (req, res) => {
await updatePassword(req.user.id, req.body.newPassword);
await destroyAllSessions(req.user.id);
res.json({ success: true, message: 'Please log in again' });
});
Auth Checklist
PDPA 2024 Consequence
Seksyen 5(1) dan 5(2) PDPA 2010 (Pindaan 2024):
- Denda sehingga RM1,000,000
- Penjara sehingga 3 tahun
Weak authentication that allows unauthorized access to personal data is kecuaian keselamatan (security negligence). Using MD5/SHA-256 without proper stretching, or having no brute-force protection, demonstrates failure to implement reasonable security measures.