ログインとトークン
毎回パスワードを送らせない
ログインができたとして、その後のリクエストはどうすればよいでしょうか。
出品のたび、購入のたびにパスワードを送らせる作りにはしません。送る回数が増えるほど漏れる機会が増えますし、受け取る側も毎回ハッシュ化して照合することになります。
代わりに、ログインが成功した時点で引換券を渡します。これがトークンです。
プレーンテキスト
1. ログイン 名前とパスワードを送る → トークンをもらう
2. その後 トークンだけを送るログインの処理
照合は、前のレッスンの逆をたどるだけです。
JavaScript
const user = db.prepare("SELECT * FROM users WHERE name = ?").get(name);
if (!user) {
return res.status(401).json({ error: "名前かパスワードが違います" });
}
const hashed = crypto.scryptSync(password, user.salt, 32).toString("hex");
if (hashed !== user.password_hash) {
return res.status(401).json({ error: "名前かパスワードが違います" });
}失敗の理由を分けない
上の 2 つの if は、どちらも同じ文言を返しています。わざとです。
JavaScript
// 避けたい
if (!user) return res.status(401).json({ error: "その利用者は存在しません" });こう分けると、「存在しません」が返るかどうかで誰が登録しているかを外から調べられます。名前の一覧を作られ、そこにパスワードを試されます。
利用者には少し不親切ですが、まとめて「名前かパスワードが違います」と返します。
トークンを作る
JavaScript
const token = crypto.randomUUID();
db.prepare("UPDATE users SET token = ? WHERE id = ?").run(token, user.id);randomUUID は、推測できない文字列を作る関数です。これを保存しておき、次のリクエストで送られてきたものと突き合わせます。
トークンは推測できてはいけません。 user.id や名前をもとに作ると、他人になりすませます。
トークンは短命にする
実務では、トークンに期限を付けます。漏れたときの被害を、その期限までに抑えるためです。
このコースでは期限までは扱いませんが、トークンはパスワードと同じくらい大切なものだという感覚は持ってください。持っている人は、その人として振る舞えます。
手を動かす
演習では、ログイン API を作ります。失敗の理由を分けないことにも気をつけてください。
要件
- 名前で利用者を探し、見つからなければ 401
- パスワードを同じ salt でハッシュ化し、保存されたものと違えば 401
- 通れば
crypto.randomUUID()でトークンを発行し、200 を返す(失敗時の文言は両方同じ)
入出力例
login("田中", "himitsu") → {"error":null,"hasToken":true,"status":200}
login("田中", "chigau") → {"error":"名前かパスワードが違います","hasToken":false,"status":401}
login("鈴木", "himitsu") → {"error":"名前かパスワードが違います","hasToken":false,"status":401}ヒント
編集 ゆめさく編集部