~/VibeHandbook
無料 PDF

18 · 04

認証と認可: 誰でも呼べるエンドポイント

この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());
});

リクエストが通らねばならない二つのゲートを思い描いてください。認証は「ログインしているか?」を問い、認可は「あなたこれをしてよいか?」を問います。侵害は、アプリが一つ目のゲートを作り、二つ目を忘れたときに起きます。

  要求 ──▶    ┌──────────────────┐  ──▶ ┌──────────────────┐  ──▶  実行  
              │ 認証(authn)      │      │ 認可(authz)       │       する
              │ "ログイン済?"    │      │ "このユーザは     │
              │                  │      │  この操作可?"     │
              └──────────────────┘      └──────────────────┘
                  ✓ 多くが実装               ✗ よく欠落   
                    する部分                 → ここが侵害

  例 — /admin/export-all-users     
     ログインユーザ ──▶ [auth ✓] ──▶ [ authz検査なし   ] ──▶ 全ユーザが流出  
     ログインユーザ ──▶ [auth ✓] ──▶ [ isAdmin? → 403  ] ──▶ ブロック  ✓

同じ罠は、いたるところに小さな形で現れる。注文 #1234 を返すのに、その注文がリクエスト元のユーザーのものかをチェックしないエンドポイント。誰でもの数字を変えれば、他人の注文を読める。ルールは地味だが絶対だ。データに触れるすべてのエンドポイントで認可をチェックし、クライアントから来たIDを信用しないこと。 隠されているからといって、隠しURLが安全だと思い込んではいけない——「これが存在することは誰も知らない」はセキュリティ対策ではない。

オフラインでも読みたい?

本編まるごとを PDF または EPUB で無料ダウンロード。