- ブラウザURL入力今ここ
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
前回は、セッションIDが実体への参照にすぎないことを見ました。今回は、持っているだけで使える値をブラウザに運ばせない、OAuth のコードを見ます。
まず結論
ブラウザが確かめられるのは「どこから来たか」までで、「本人が望んだか」はサーバが自分で確かめるしかありません。OAuth の認可コードフローはこの前提どおりに作られていて、ブラウザを通る経路には短命で1回限りの認可コードだけを載せ、アクセストークンはクライアントと認可サーバの直接の通信で受け渡します。
機密クライアントなら、認可コードはそれだけでは交換できません。交換を求めているのがそのクライアント本人かを、認可サーバが直接の通信の中で認証するからです。
OAuth 2.0 の認可コードフローで、利用者が同意したあと、認可サーバがブラウザへのリダイレクト(Location の URL)に載せて返すのはどれでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
持っているだけで使えるものを、ブラウザに運ばせない
『セキュリティとAIの仕組み』の「「本人である」の作り方 — 認証と認可は別物」で、RFC 6750 の Bearer トークンの定義を見ました。持っているだけで使える、という定義です。
リダイレクトで運ばれる値は、URL としてあちこちに残ります。RFC 6819 §3.4 は、トークンの代わりに認可コードを送る目的を2つ挙げています。
- Browser-based flows expose protocol parameters to potential attackers via URI query parameters (HTTP referrer), the browser cache, or log file entries, and could be replayed. In order to reduce this threat, short-lived authorization "codes" are passed instead of tokens and exchanged for tokens over a more secure direct connection between the client and the authorization server.
- It is much simpler to authenticate clients during the direct request between the client and the authorization server than in the context of the indirect authorization request. The latter would require digital signatures.
要は2つ目、クライアントを確かめる場所を直接の要求に移す点です。RFC 6749 §1.3.1 も同じ2点を挙げています(the ability to authenticate the client と、アクセストークンを without passing it through the resource owner's user-agent で渡せること)。
- ・「表経路」「裏経路」は教材側の呼び名。RFC 6819 は後者を direct connection と呼ぶ
- ・ブラウザが見るのは code まで。アクセストークンはブラウザを通らない
おもちゃの認可サーバで、どこに何が流れるかを見る
127.0.0.1 上に、自前のおもちゃの認可サーバとクライアントを立てました(実在の認可サーバではありません。利用者 alice を自動で承認します)。ブラウザ役は curl です。まず表経路です。
> GET /authorize?response_type=code&client_id=web-app&redirect_uri=http%3A%2F%2F127.0.0.1%3A18743%2Fcallback&state=st-123 HTTP/1.1< HTTP/1.1 302 Found< Location: http://127.0.0.1:18743/callback?code=yPX47Vy_0WrilcZnVzxbSl2qqRu1B46r&state=st-123
認可コードは Location のクエリに載っています。RFC 6819 §4.4.1.1 が書くとおり Web server request logs commonly include query parameters on requests. なので、この値はクライアント側のアクセスログに残りえます。
ブラウザ役が別の認可要求で得た Location を開くと、クライアントはコードを受け取り、裏経路で交換します。クライアントのログです。
[client] front channel: received request line "GET /callback?code=AhL9ZS4kKzk5DIkOdIEeap7He2Osw9iI&state=st-456"[client] back channel: POST http://127.0.0.1:18742/token body=grant_type=authorization_code&code=AhL9ZS4kKzk5DIkOdIEeap7He2Osw9iI&redirect_uri=http%3A%2F%2F127.0.0.1%3A18743%2Fcallback&client_id=web-app&client_secret=<redacted>[client] back channel: response 200 {"access_token":"9xkmjOPpXzdUkQIxlqSzdQJOD15tDN16","token_type":"Bearer","expires_in":300}
このときブラウザ役に返ったのは client: login finished (token kept server-side, not shown to the browser) の1行だけです。アクセストークンは、ブラウザ側の出力のどこにも出てきません。
おもちゃの認可サーバとクライアントを使った実演で、アクセストークンの値が出てきたのはどこですか。
コードの価値を絞る — ただし効くのは機密クライアント
表経路を通る以上、認可コードは見られることがあります。そこで仕様は、コードの価値を絞っています。RFC 6749 §4.1.2 の code の定義です。
The authorization code MUST expire shortly after it is issued to mitigate the risk of leaks. A maximum authorization code lifetime of 10 minutes is RECOMMENDED. The client MUST NOT use the authorization code more than once. If an authorization code is used more than once, the authorization server MUST deny the request and SHOULD revoke (when possible) all tokens previously issued based on that authorization code. The authorization code is bound to the client identifier and redirection URI.
義務には主語と強さがあります。表の機密クライアントは、資格情報の秘密を保てるクライアントです(RFC 6749 §2.1 Clients capable of maintaining the confidentiality of their credentials)。
主語 強さ 内容認可サーバ MUST 発行後すぐに失効させる(上限の目安) RECOMMENDED 最大10分クライアント MUST NOT 同じコードを2回使う認可サーバ MUST 2回目の要求を拒否する認可サーバ SHOULD そのコードから出したトークンを取り消すクライアント MUST 機密クライアントならクライアント認証を行うクライアント MUST redirect_uri を認可要求に含めた場合は同じ値を送る
加えて RFC 9700 §2.1 は、登録済みのリダイレクト URI との比較に exact string matching を MUST としています(ネイティブアプリの localhost のポート番号は例外)。
おもちゃの認可サーバはこれらを実装しています。
### [4] same code a 2nd time (with the correct client secret)< HTTP/1.1 400 Bad Request{"error":"invalid_grant","error_description":"code already used"}### [5] resource with the earlier token after the reuse attempt{"error":"invalid_token"}### [6] fresh code, redeemed WITHOUT the client secret{"error":"invalid_client"}HTTP 401### [7] authorize with a redirect_uri that differs by one trailing slash{"error":"invalid_request"}HTTP 400
同じコードの2回目([4])、シークレット無しの交換([6])、末尾スラッシュ1文字違い([7])がすべて拒否されました。2回実行して結果は同じでした。
[5] で1回目のアクセストークンまで無効になったのは、SHOULD revoke を実装したからです。SHOULD なので、実在のサーバが必ずこう動くとは限りません。 10分の失効は、この実演では試していません。
ACME の token も、それだけでは何も証明しない
『Webを支えるソフトウェア』の「証明書は誰が更新するのか — ACMEと期限」では、ACME のチャレンジを扱いました。そこで触れなかった仕組みが、RFC 8555 §8.1 の key authorization です。
keyAuthorization = token || '.' || base64url(Thumbprint(accountKey))
HTTP チャレンジ(§8.3)では、CA から渡された token 値を /.well-known/acme-challenge/ に続くパスとして使い、そこに keyAuthorization を置きます。確認の要求は TCP ポート80 の HTTP で送られます。RFC が明言してはいませんが、この設計からは、token が暗号化されない経路に載る前提だと読めます。
効力は token 単独からは生まれません。§10.2 は、認可の完全性が、チャレンジを次の両方を満たす者だけが完了できるようにすることにかかっている、と書いています。
can only be completed by someone who both (1) holds the private key of the account key pair and (2) controls the identifier in question.
見られてよい値が、秘密のアカウント鍵と結び付いて初めて権限になります。
RFC 6749 §4.1.2 は、認可コードの有効期間について何を定めていますか。
OAuth の認可コード
クエリに載って運ばれ、ログにも残りえます。交換には同じ redirect_uri と、機密クライアントならクライアント認証が要り、短命・1回限りの制約も掛かります。
見えうる値 code(クエリに載る)結び付ける先 client_id・redirect_uri・クライアント認証追加の制約 すぐに失効(MUST)・1回限り
ACME HTTP チャレンジの token
暗号化しない HTTP の URL パスにそのまま置かれます。効力は、アカウント鍵のフィンガープリントと連結した keyAuthorization から生まれます。
見えうる値 token(URL パスに載る)結び付ける先 アカウント鍵のフィンガープリント追加の制約 (秘密にすることは求められていない)
片方は認可の委譲、もう片方は証明書の発行です。見られうる値の扱い方に、共通する構造は何でしょうか。
「この値を拾った第三者は、それだけで何ができるか」を両方で考えてみてください。
この章のまとめ
- 表経路には認可コードだけを載せ、アクセストークンは直接の通信で受け渡す(RFC 6819 §3.4 の2目的)。
- コードはログに残りうるので価値を絞る。すぐに失効(MUST。10分は RECOMMENDED)、1回限り、client_id と redirect_uri に結び付け。単独で使えないのは機密クライアントだけで、公開クライアントは次の回の PKCE へ。
- 取り消し([5])は SHOULD を実装した結果で、実在のサーバが必ずこう動くとは限らない。
- implicit grant について RFC 9700 §2.1.2 は SHOULD NOT … unless。「禁止」は言い過ぎ。
- ACME の token も、アカウント鍵に結び付けて初めて効く。ただし公開を前提にした値である点が違う。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
RFC 6819 §3.4 は、リダイレクト URI にトークンではなく認可コードを送る目的を2つ挙げています。その組み合わせはどれですか。
用語集(3語)
- 認可コード (authorization code)
- 利用者が同意したあとに、認可サーバがリダイレクトでクライアントへ渡す中間値。発行後すぐに失効させ(MUST)、上限10分が推奨(RECOMMENDED)。クライアント識別子とリダイレクト URI に結び付けられる(RFC 6749 §4.1.2)。
- 表経路・裏経路
- この教材での呼び名。表経路は、ブラウザのリダイレクトで運ばれる間接的な経路。裏経路は、クライアントのサーバから認可サーバへ直接送る要求。RFC 6819 §3.4 は後者を `direct connection` と呼ぶ。
- keyAuthorization
- ACME のチャレンジで置かせる文字列。`token || '.' || base64url(Thumbprint(accountKey))`(RFC 8555 §8.1)。token をアカウント鍵に結び付ける。
出典(一次ソース)
- RFC 6749 — The OAuth 2.0 Authorization Framework(§1.3.1, §2.1, §2.2, §4.1.2, §4.1.3, §4.2.2) ↗
- RFC 7636 — Proof Key for Code Exchange by OAuth Public Clients(§1) ↗
- RFC 6819 — OAuth 2.0 Threat Model and Security Considerations(Informational、§3.4, §4.4.1.1) ↗
- RFC 9700 — Best Current Practice for OAuth 2.0 Security(BCP 240、January 2025、§2.1, §2.1.2) ↗
- RFC 8555 — Automatic Certificate Management Environment (ACME)(§8.1, §8.3, §10.2) ↗
- RFC 9110 — HTTP Semantics(§7.1) ↗