แฮชรหัสผ่านด้วยฟังก์ชันที่มีต้นทุนสูง เลือกระหว่าง session กับ JWT อย่างเข้าใจถ่องแท้ และปกป้อง route ของคุณด้วย middleware
เปิดบทเรียนนี้ใน Kodokonการเก็บรหัสผ่านหมายถึงการเตรียมพร้อมรับการรั่วไหลในอนาคต จงออกแบบการแฮชให้อยู่รอดได้แม้ฐานข้อมูลถูกขโมย แฮชที่เร็วอย่าง SHA-256 สามารถถูกเดาแบบ brute-force ได้หลายพันล้านครั้งต่อวินาทีบน GPU คุณจำเป็นต้องมีฟังก์ชันที่ จงใจให้มีต้นทุนสูง อย่าง scrypt ที่มากับ node:crypto หรือ bcrypt/argon2 พร้อมด้วย salt แบบสุ่มต่อผู้ใช้แต่ละคน ซึ่งทำให้ตารางที่คำนวณไว้ล่วงหน้าไร้ประโยชน์ salt ไม่ใช่ความลับ มันถูกเก็บเป็นข้อความธรรมดาไว้ข้าง ๆ แฮช
import {
randomBytes,
scryptSync,
timingSafeEqual,
} from 'node:crypto';
export function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const hash = scryptSync(password, salt, 64)
.toString('hex');
return `${salt}:${hash}`;
}
export function verifyPassword(password, stored) {
const [salt, hash] = stored.split(':');
const candidate = scryptSync(password, salt, 64);
const expected = Buffer.from(hash, 'hex');
return timingSafeEqual(candidate, expected);
}มีสองแบบจำลองสำหรับการทำให้ผู้ใช้ยังคงล็อกอินอยู่ session คือ ตัวระบุที่ทึบในคุกกี้ httpOnly โดยมีสถานะอยู่ที่ฝั่งเซิร์ฟเวอร์ เพิกถอนได้ทันที คุกกี้ที่ JavaScript มองไม่เห็น แต่ที่เก็บร่วม (Redis) จะกลายเป็นสิ่งจำเป็นทันทีที่คุณเพิ่มอินสแตนซ์ตัวที่สอง ส่วน JWT คือ สถานะที่ถูกเซ็นฝังอยู่ในโทเคน ตรวจสอบได้โดยไม่ต้องมีที่เก็บใด ๆ เหมาะอย่างยิ่งระหว่างเซอร์วิส แต่ เพิกถอนไม่ได้ ก่อนหมดอายุ การแลกเปลี่ยนตามจริงคือ ใช้ session สำหรับเว็บแอปพลิเคชันแบบคลาสสิก และใช้ JWT อายุสั้นสำหรับ API ที่ถูกเรียกใช้โดยบุคคลที่สาม หรือสำหรับสถาปัตยกรรมแบบกระจาย
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET;
if (!SECRET) {
throw new Error('JWT_SECRET must be set');
}
export function signToken(user) {
return jwt.sign(
{ sub: user.id, email: user.email },
SECRET,
{ expiresIn: '15m' },
);
}middleware ป้องกันดึงเอาการควบคุมการเข้าถึงออกมาเป็นส่วนกลาง มันดึงโทเคนจาก header Authorization: Bearer ตรวจสอบมัน แนบตัวตนที่ถอดรหัสแล้วไว้ที่ req.user หรือตัดจบด้วย 401 ที่ถูกส่งไปยัง error middleware จากบทที่แล้ว จงใช้มันในระดับ router (router.use(requireAuth)) เพื่อปกป้องกลุ่ม route ทั้งกลุ่ม แทนที่จะทำทีละ route
import jwt from 'jsonwebtoken';
import { HttpError } from './http-error.js';
const SECRET = process.env.JWT_SECRET;
export function requireAuth(req, res, next) {
const header = req.headers.authorization ?? '';
const [scheme, token] = header.split(' ');
if (scheme !== 'Bearer' || !token) {
return next(new HttpError(401, 'Missing token'));
}
try {
req.user = jwt.verify(token, SECRET);
next();
} catch {
next(new HttpError(401, 'Invalid or expired token'));
}
}timingSafeEqual มีไว้เพื่ออะไรในการตรวจสอบรหัสผ่าน?