+ 作る
図を読み込んでいます…
1 / 13
図でつながる 認証と暗号の基礎 / 全11章

第1章 認証と認可 ―「あなたは誰?」と「それをしてよい?」を分ける

発行
かえで
版
第2版
改訂
分量
13コマ・6,041字

ログインしたのに、他人の下書きは編集できない。アオイの一つのリクエストを追いながら、「あなたは誰?」を決める認証と「それをしてよい?」を決める認可の違いを、401と403の違いまで動く図で確かめます。

ログインしたのに、編集を断られる

この本では、Webアプリの安全を支える「認証」と「暗号」の基本を、図を動かしながら順に見ていきます。最初の章は、暗号の話に入る前の土台になる二つの言葉、認証と認可です。

アオイはアニメモにログインして、作品を眺めていました。ふと、ボブの下書き「春の旅」のページで編集ボタンを押します。ログインはちゃんと成功しています。ところがサーバーからは「編集できません」と返ってきました。図の右で、サーバーが赤い札を返しているところです。

アオイが本物のアオイであることは、サーバーも分かっています。それでも断るのは、この下書きの持ち主がボブだからです。「誰がリクエストしているか」と「その人にこの操作を許すか」は、別々に確かめる問いなのです。この章では、この二つの問いがどこで、どうやって確かめられるのかを、一つのリクエストを追いながら見ていきます。

  • ログインしたのに、編集を断られる
  • ボブの下書き「春の旅」
  • サーバー
  • 書類
  • 編集したい
  • 人
  • サーバー
  • 持ち主:ボブ
  • アオイ
  • 編集できません
  • ログイン済み
  • 押す
  • 編集
  • ログインは成功している。それでも断られた。

二つの関門:「あなたは誰?」と「それをしてよい?」

サーバーの中には、リクエストが通る関門が二つあると考えると分かりやすくなります。一つ目は認証(authentication)で、「あなたは誰?」を確かめます。二つ目は認可(authorization)で、「その人に、この対象へのこの操作をさせてよい?」を判断します。

図では、アオイの「編集したい」というリクエストが左から進んでいきます。一つ目の関門では、ログインしたアオイだと確かめられて緑の印が付きました。二つ目の関門では、対象がボブの下書きなので赤い印が付き、そこで止まります。

英語ではどちらも auth で始まり、まとめて「auth」と書かれることもあるので、混ざりやすい言葉です。けれど働きははっきり違います。認証が通ると「誰か」が決まり、認可はその「誰か」を前提にして、操作ごとに答えを出します。ログインの成功は、一つ目の関門を通っただけです。OWASP の認可のチートシートも、この二つを区別するところから話を始めています。

  • 二つの関門:「あなたは誰?」と「それをしてよい?」
  • 認証
  • 認可
  • OK
  • NG
  • 人
  • 書類
  • 編集したい
  • アオイ
  • ボブの下書き
  • あなたは誰?
  • それをしてよい?
  • ログインの成功は、一つ目の関門を通っただけ。

認証の手段はいろいろ、結果はひとつ

「あなたは誰?」を確かめる方法はいくつもあります。昔からあるのはパスワードで、本人だけが知っている文字列を入力してもらいます。パスキーは、利用者の端末の中にしまった鍵を使って本人であることを示す仕組みで、偽のサイトに入力させられて盗まれる心配がずっと小さくなります。「Google でログイン」のような外部のログインは、ほかのサービスに本人確認を任せて、その結果を受け取る方法です。

図の左の三枚のカードは、どれも同じ「認証」の関門につながっています。手段は違っても、関門から出てくるものはひとつです。それが右の「主語 = アオイ」という結論、つまり「このリクエストはアオイとして扱える」という情報です。

ここで確かめているのは、アニメモのアカウント「アオイ」の持ち主であることです。戸籍上の名前や身分証まで確かめるのは、また別の手続きになります。アプリにとって大事なのは、このリクエストをどのアカウントのものとして扱うか、つまりリクエストの主語が決まることです。パスキーの鍵の仕組みは公開鍵暗号と電子署名の章で、外部のログインは OAuth と OIDC の章で詳しく見ます。

  • 認証の手段はいろいろ、結果はひとつ
  • パスワード
  • 認証
  • 人
  • パスキー
  • OK
  • 主語 = アオイ
  • アオイ
  • 外部のログイン
  • あなたは誰?
  • どの手段でも、得られるのは「このリクエストはアオイとして扱える」

ログインのあとは、番号札を見せる

アオイは編集ボタンを押すたびにパスワードを打っているわけではありません。それでもサーバーは、毎回のリクエストがアオイのものだと分かります。その秘密は、ログインの直後にサーバーが渡す番号札にあります。

ログインに成功すると、サーバーは推測できない長い乱数(セッション ID)を作り、「この番号はアオイ」と自分の手元に記録します。そしてその番号を Cookie(ブラウザが覚えておき、同じサイトへのリクエストに自動で付けて送る小さなデータ)としてアオイのブラウザに渡します。図では、上の矢印でログインし、下の矢印でピンクの番号札がアオイの側に届きました。サーバーの手元には、番号と利用者の対応表が残っています。

劇場のクロークでコートを預けると、番号札をもらうのと似ています。帰りは番号札を見せるだけで、係の人が記録と照らしてコートを返してくれます。ただし、たとえには限界があります。番号札を拾った人もコートを受け取れてしまうように、セッション ID を盗まれると、その人がアオイになりすませます。そのため Cookie には HttpOnly や Secure などの設定を付けて盗まれにくくします(OWASP のセッション管理のチートシート)。番号札の代わりに、中身に署名したトークンを渡す方式もあり、JWT の章で扱います。

  • ログインのあとは、番号札を見せる
  • ログイン(パスワードなど)
  • 人
  • サーバー
  • セッション Cookie sid=7f3a…
  • アオイ
  • サーバー
  • 番号札を渡す(Set-Cookie)
  • sid=7f3a… → アオイ
  • パスワードを送るのは最初の一回。あとは番号札で足りる。

サーバーは毎回、番号札から主語を決める

ログインのあと、アオイが編集ボタンを押すと、ブラウザは「下書き 42 を編集したい」というリクエストに、さきほどの Cookie を自動で添えて送ります。図では、白いリクエストとピンクの番号札がいっしょにサーバーへ運ばれていきます。

サーバーは届いた番号を手元の対応表と照らします。「sid=7f3a… はアオイ」と見つかれば、このリクエストの主語はアオイに決まります。見つからない番号や期限切れの番号なら、主語は決まりません。この照合はリクエストのたびに行います。途中でログアウトしたり、番号札の期限が切れたりすることがあるからです。

HTTP は、リクエストごとに話が完結する仕組みです。サーバーは前のやり取りを自動では覚えていません。だからこそ、毎回のリクエストに「誰か」を示す証拠を付けてもらい、サーバーがそれを検証して主語を決めます。パスワードを打つのは最初の一回だけで、あとは番号札が身元の証拠になります。

  • サーバーは毎回、番号札から主語を決める
  • サーバー
  • 人
  • PATCH /drafts/42
  • サーバー
  • 毎回 Cookie が付く
  • セッション Cookie sid=7f3a…
  • sid=7f3a… → アオイ
  • アオイ
  • 主語 = アオイ
  • 見つからない番号や期限切れの番号なら、主語は決まらない。

自己申告の user_id は信じない

ここで、少し意地悪な実験をしてみます。アオイがブラウザの開発者ツールを使い、リクエストの中身に user_id=bob と書き足して送りました。「私はボブです」と名乗っているわけです。図の赤いカードがそれです。

サーバーはこの申告を無視します。主語を決める材料にするのは、サーバー自身が発行して手元で照合できる番号札だけです。番号札が示すのはアオイなので、主語はアオイのままです。図では、番号札に緑の印、user_id=bob に赤い印が付いています。

リクエストの中身は、送り手が自由に作れます。フォームの隠し項目も、URL の後ろの値も、JSON の中の項目も、好きに書き換えて送れます。ですから「誰か」は、送り手が書いた値ではなく、サーバーが検証できる証拠から決めます。この考え方は、このあと出てくる認可の判定でも、データベースへの書き込みでも、ずっと使います。

  • 自己申告の user_id は信じない
  • サーバー
  • PATCH /drafts/42
  • 人
  • セッション Cookie sid=7f3a…
  • OK
  • サーバー
  • user_id=bob
  • NG
  • sid=7f3a… → アオイ
  • アオイ
  • 主語 = アオイ
  • 主語は、送り手が書いた値ではなく、サーバーが確かめた証拠で決める。

認可は「主語・対象・操作」の組み合わせ

主語が決まったら、二つ目の関門の出番です。認可では、誰が(主語)、何に(対象)、何をするか(操作)の三つを組にして、アプリのルールに照らします。アニメモなら「下書きは持ち主だけが編集できる」「購入した教材は購入者が閲覧できる」といったルールです。

図では、サーバーで決まった主語のカードが左の枠に移り、三つの枠に「アオイ」「アオイの下書き」「編集」がそろいました。対象の持ち主がアオイなのでルールに合い、緑の「許可」が出ます。同じアオイでも、組み合わせが変わると答えは変わります。

主語対象操作判定
アオイアオイの下書き編集許可
アオイボブの下書き編集拒否
アオイボブの公開作品閲覧許可

表の三行目のように、ボブの作品でも、公開されていれば閲覧は許されます。認可は「この人は信用できるか」という一つのスイッチではなく、組み合わせごとの答えです。OWASP のチートシートは、必要な権限だけを与えることと、どのルールにも当てはまらないものは拒否することを勧めています。

  • 認可は「主語・対象・操作」の組み合わせ
  • 主語(誰が)
  • 対象(何に)
  • 操作(何を)
  • アオイ
  • ×
  • アオイの下書き
  • ×
  • 編集
  • 許可
  • 持ち主:アオイ
  • ルール:下書きは、持ち主だけが編集できる
  • 同じアオイでも、対象や操作が変われば答えが変わる。

対象が変わると、答えも変わる

さきほどの組み合わせのうち、対象だけを入れ替えてみます。図の真ん中の枠が「ボブの下書き」に変わり、その下の持ち主も「ボブ」になりました。主語のアオイも、操作の編集もそのままです。

ルールは「下書きは持ち主だけが編集できる」でした。持ち主はボブで、主語はアオイなので、ルールに合いません。右の判定は赤い「拒否」に変わります。冒頭の場面でアオイが編集を断られたのは、この判定のためです。

ボブがアオイを共同編集者に招いていれば、話は変わります。そのときはルールを「持ち主か、招かれた共同編集者なら編集できる」とし、招待の記録を確かめて許可します。どんな場合でも、判定の材料になるのは主語・対象・操作と、サーバーが持っている記録です。画面でボタンが押せたかどうかは、判定の材料に入りません。

  • 対象が変わると、答えも変わる
  • 主語(誰が)
  • 対象(何に)
  • 操作(何を)
  • アオイ
  • ×
  • ボブの下書き
  • ×
  • 編集
  • 拒否
  • 持ち主:ボブ
  • ルール:下書きは、持ち主だけが編集できる
  • 持ち主はボブ、主語はアオイ。ルールに合わないので拒否。

ID を書き換えても、ボタンを隠しても

アオイがボブの下書きを狙うとしたら、どうするでしょう。編集画面の URL は animemo.app/drafts/42/edit のように、作品の ID を含んでいます。この 42 を、ボブの下書きの 57 に書き換えて開けばよいのです。図の上の URL がその書き換えです。サーバーが「ログイン済みなら通す」だけなら、これでボブの下書きが開けてしまいます。この種の穴は IDOR(Insecure Direct Object Reference、オブジェクトを直接指す参照の不備)と呼ばれ、実際のサービスでもよく見つかります(OWASP の IDOR 対策のチートシート)。

画面から編集ボタンを隠しても防げません。図の下のように、ブラウザを使わずにプログラムから API へ直接リクエストを送れるからです。画面は利用者への案内役で、守りの役は受け持っていません。

守りはサーバーの認可の関門にあります。どの経路から来たリクエストでも、サーバーは毎回、主語・対象・操作を確かめます。ID を推測しにくい UUID にすると見つけられにくくはなりますが、ID は共有リンクや履歴から漏れることがあります。推測しにくい ID は、毎回の確認を補う工夫として使います。

  • ID を書き換えても、ボタンを隠しても
  • animemo.app/drafts/57/edit
  • 42 → 57
  • ブラウザ
  • 認可
  • サーバーで 毎回確かめる
  • 画面(ボタンなし)
  • ターミナル
  • NG
  • 拒否:ボブの下書き
  • API を直接呼ぶ
  • ID を推測しにくくしても、毎回の確認は省けない。

判定をデータの読み書きに結び付ける

認可の判定と、データベースへの書き込みが別々の場所にあると、どこかで判定を書き忘れる処理が出てきます。そこで、書き込みの条件そのものに主語を入れておく方法がよく使われます。

sql
UPDATE drafts
SET title = $1
WHERE id = $2
  AND user_id = $3
RETURNING id;

$2 は書き換えたい下書きの ID、$3 はサーバーが番号札から決めた主語の ID です。$3 を画面から受け取らない理由は、自己申告の user_id のページで見たとおりです。図では、アオイが 57 番を書き換えようとしています。57 番の持ち主はボブなので、「id が 57」と「user_id がアオイ」の両方に当てはまる行はなく、更新される行は 0 件です。

0 件のときは「成功」と返さずに、拒否として扱います。ここを見落とすと、データは守れても、画面に「保存しました」と出てしまいます。読み出しも同じ考え方で、有料のページを購入者だけに見せるなら、購入の記録を条件に入れて取り出し、未購入の人への返事には本文を含めません。

  • 判定をデータの読み書きに結び付ける
  • データベース
  • UPDATE drafts SET … WHERE id = 57 AND user_id = アオイ
  • drafts 表
  • id 42 持ち主 アオイ
  • id 57 持ち主 ボブ
  • NG
  • 主語 = アオイ
  • 一致 0 件 → 成功にしない
  • user_id は画面から受け取らない。番号札から決めた主語を使う。

401 と 403:どちらの関門で止まったか

断られたとき、サーバーは HTTP のステータスコードで「どちらの関門で止まったか」を伝えます。HTTP の意味を定めた RFC 9110 にある、二つの番号を見てみましょう。

401 Unauthorized は、一つ目の関門で止まったことを表します。番号札が付いていない、期限が切れている、偽物だった、といった理由で「誰か」が分からないときです。名前に Unauthorized(認可されていない)とあって紛らわしいのですが、意味は「認証が必要です」に近いものです。仕様では、401 を返すときに、どう認証すればよいかを示す WWW-Authenticate ヘッダーを付けることになっています。

403 Forbidden は、二つ目の関門で止まったことを表します。誰かは分かったけれど、その操作は許されないときです。RFC 9110 の 403 の説明にも、認証情報が付いていたなら、サーバーはそれでは足りないと判断した、とあります。図の上の段は番号札なしで 401、下の段はアオイの番号札を付けてボブの下書きを編集しようとして 403 です。

なお、他人の非公開の作品があること自体を知られたくないときは、403 の代わりに 404 Not Found を返してもよいとされています。

  • 401 と 403:どちらの関門で止まったか
  • 認証
  • 認可
  • 番号札なし
  • 401
  • 書類
  • 番号札あり
  • OK
  • ボブの下書き
  • 403
  • 誰か分からない
  • 誰かは分かったが、許されない
  • 401 は「名乗ってください」、403 は「名乗っても通せません」。

OAuth・OIDC・JWT は担当が違う

認証や認可の話をしていると、OAuth、OIDC、JWT という名前がよく出てきます。どれもこの本の後半で一章ずつ扱う道具で、受け持つ仕事がそれぞれ違います。

OAuth は、利用者が「このアプリに、私のデータのこの範囲だけ触らせてよい」と任せるための仕組みで、アプリはその許しをアクセストークンとして受け取ります。OIDC(OpenID Connect)は OAuth の上に作られた仕組みで、「この利用者が誰としてログインしたか」という認証の結果をアプリに伝えます。JWT は、そうした情報を署名つきで運ぶためのデータの形式です。図の左の三枚は、それぞれ「任せる」「伝える」「運ぶ」を担当しています。

三つのどれを使っても、図の右にある関門、つまり「アオイがボブの下書きを編集してよいか」を決めるのはアプリ自身です。正しいトークンが届いたことが示すのは、主語が決まったこと、あるいは決められた範囲を任されたことまでです。対象の持ち主を確かめる判定は、どの道具を使ってもアプリの側に残ります。

  • OAuth・OIDC・JWT は担当が違う
  • OAuth アクセスを任せる
  • 認可
  • OIDC ログインの結果を伝える
  • アプリ自身が 判断する
  • 書類
  • ボブの下書き
  • JWT 情報を署名つきで運ぶ形式
  • どの道具を使っても、最後の「してよい?」はアプリが答える。

まとめ:ログイン済みのアオイは、ボブの下書きを編集できる?

この章では、一つのリクエストが二つの関門を通る様子を追いました。認証は「あなたは誰?」に答えて、リクエストの主語を決めます。ログインのあとは、サーバーが渡した番号札(セッションの Cookie やトークン)を毎回検証して主語を決め、送り手が書いた user_id は信じません。認可は「それをしてよい?」に答えて、主語・対象・操作の組み合わせをルールに照らします。

では、ログイン済みのアオイは、ボブの下書きを編集できるでしょうか。答えは、ボブから共同編集者に招かれているなどの許可がない限り、できません。認証に成功したので、アオイだということは分かっています。けれど対象の持ち主はボブで、ルールに合いません。サーバーは 403 を返し、更新の SQL も 0 件で終わります。図では、同じ「主語 = アオイ」から出た二本の矢印が、対象によって許可と拒否に分かれています。

ところで、一つ目の関門でアオイのパスワードを確かめるには、サーバーはパスワードを覚えておく必要がありそうです。けれど、パスワードをそのまま保存していると、データベースが漏れたときに全員のパスワードが知られてしまいます。次の章では、元に戻せない「指紋」を作るハッシュ関数を使って、パスワードをどう保管するかを見ていきます。

  • まとめ:ログイン済みのアオイは、ボブの下書きを編集できる?
  • 認証
  • アオイの下書き × 編集 → 許可
  • 人
  • OK
  • 主語 = アオイ
  • アオイ
  • ボブの下書き × 編集 → 拒否
  • 次の章:パスワードは、どう保管する?(ハッシュ関数)

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]