コストの高い関数でパスワードをハッシュ化し、セッションとJWTを十分に理解したうえで選び、ミドルウェアでルートを保護しましょう。
このレッスンを Kodokon で開くパスワードを保存するということは、未来の侵害に備えるということです。データベースが盗まれても生き延びるように、ハッシュ化を設計しましょう。SHA-256のような速いハッシュは、GPU上で毎秒数十億回の試行で総当たりされてしまいます。必要なのは意図的にコストの高い関数、node:cryptoが提供するscryptか、bcrypt/argon2、そしてユーザーごとのランダムなソルトです。ソルトは事前計算されたテーブルを無用にします。ソルトは秘密ではありません。ハッシュのすぐ隣に平文で保存されます。
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);
}ユーザーをログインしたままにする二つのモデルがあります。セッションは、httpOnlyクッキーの中の不透明な識別子で、状態はサーバー側にあります。即時の失効、JavaScriptから見えないクッキー。しかし二つ目のインスタンスを追加した途端に、共有ストア(Redis)が必要になります。JWTは、署名された状態がトークンに埋め込まれ、いかなるストアもなしに検証できます。サービス間には理想的ですが、有効期限が切れる前は失効できません。正直なトレードオフはこうです。古典的なウェブアプリケーションにはセッションを、サードパーティに消費されるAPIや分散アーキテクチャには短命のJWTを、です。
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' },
);
}保護ミドルウェアはアクセス制御をくくり出します。Authorization: Bearerヘッダーからトークンを取り出し、それを検証し、デコードされた身元をreq.userに添付するか、あるいは前のレッスンのエラーミドルウェアに回される401で話を打ち切ります。ルートごとにではなく、ルートのグループ全体を保護するために、これをルーターのレベル(router.use(requireAuth))で適用しましょう。
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は何のためにありますか?