認証と認可: 誰でも呼べるエンドポイント
この2つの言葉は似て聞こえるが、その違いこそが本当の侵害が潜む場所だ。
- 認証 (Authentication) はあなたは誰か?——ログインし、身元を証明すること。
- 認可 (Authorization) はあなたは何をしてよいか?——このログイン済みのユーザーが、このデータに対してこの操作を行ってよいかどうか。
AIは認証はそこそこ得意だ。ライブラリがほとんどを処理してくれる。AIが日常的に失敗するのは認可のほうだ。認可はあなたのアプリのルールに固有のもので、AIはそれを知らないからだ。教科書どおりの惨事はこうだ。
// VULNERABLE: ログイン済みかはチェックするが、誰なのかはチェックしない
app.get("/admin/export-all-users", requireLogin, (req, res) => {
res.json(db.getAllUsers()); // ログイン済みなら誰でもここを叩ける
});
このエンドポイントは「保護されている」——ログインしていなければならない。だがどんなログイン済みユーザーでも、30秒前にサインアップした人を含めて、これを呼んで全ユーザーのデータをダウンロードできる。認証はあり、認可はない。修正方法は、操作そのものに対して権限をチェックすることだ。
// SAFE: このユーザーが本当に管理者かを確認する
app.get("/admin/export-all-users", requireLogin, (req, res) => {
if (!req.user.isAdmin) return res.status(403).send("Forbidden");
res.json(db.getAllUsers());
});
同じ罠は、いたるところに小さな形で現れる。注文 #1234 を返すのに、その注文がリクエスト元のユーザーのものかをチェックしないエンドポイント。誰でもURLの数字を変えれば、他人の注文を読める。ルールは地味だが絶対だ。データに触れるすべてのエンドポイントで認可をチェックし、クライアントから来たIDを信用しないこと。 隠されているからといって、隠しURLが安全だと思い込んではいけない——「これが存在することは誰も知らない」はセキュリティ対策ではない。