TL;DR: スコープは委任の場合のみ役割を果たし、ユーザーに代わってアプリができることを制限するので、スコープはユーザーができること以上のことをアプリケーションができるようにはしません。スコープを使用可能な範囲以上に伸ばすことはできますが、どこにでもいる無邪気な語り口で説得しようとする説明に反して、簡易でも普通でもありません。

「@vibronet すべての認証シナリオに OAuth2 スコープを使うのは良い方法ではない理由。」

問題

OAuth2 スコープは誤解されています。デベロッパーは考えを抱いていないデベロッパーでさえも、スコープは認証と関わりがある OAuth2 でプリミティブに定義されている唯一のもので、その結果、人々はあらゆる認証シナリオで使用する という、「ある解決策や手段を持っている人間」シンドロームの被害者です。丁度、ニュートンの法則が簡単な問題の取り組みには十分なように、無邪気な使用範囲にも同じことが言えます。アーキテクチャーがずっと複雑になると、人々は面倒から逃れるために飛躍的な条件で考え直す必要があることを発見します(しばしば、嫌というほど)。

確かに、プロトコルの仕様がこの点に関して規範的過ぎないものも役に立ちませんが(OAuth2 の普通にみられる病)、深く読み込むことで、ものごとが明確になるものです。一方は状況証拠/仕様の意図や背景、他方は「物理法学」では解釈の余地があまりありません。

本書でお伝えする点で最も多い誤解は正当な認証要件からくるもので、スコープはたたたまその場に居合わせた誤ったアーティファクトだということを強調させていただきます。しいて言えば、本書は内容についてさらに規範的な指導の必要性を指摘しています(入門書は、Identiverse での Jared Hanson 氏のセッションをご覧ください)。

本書では、自分の信念が挑発されたときに強く押し返す人々がいることを仮定して、ここで実行することをさらに明確に説明しています。

現実に基づいて存在しないもののように、スコープのセマンティックはまったく想像のもの、つまり、言葉の意味では誰もが同意する「合意による幻覚」ですから、ユーザーは正式に本投稿の根拠をすべて拒否する自由があり、お好みの意味で「スコープ」を使用できます。ここで使用する主な選択基準は、ソシューションを現実にする決定を考慮するときに、「良い思考を考える」最善の助けとなる意義のある選択は何か、ということです。これは非常に相対的で、非常に恣意的なもので、直線的に計れるものではありません。素数のヒューリスティックはとても重宝なもので、使用することもできますが、一定数を超えて使うと使用できません。私たちが意図する範囲で使えるかを試すか、あるいは過度の単純化でめちゃくちゃになり、そのプロセスをやり直ししなければならなくなります。スコープでも同じことが言えます。過度に単純化された方法で実行してプログラミングが簡単そうなので嬉しくなりますが、さらに複雑な場面に出くわすと疲れ果ててしまい、重大なプログラミング問題を修正しなければならなくなります。

スコープの取得

あらゆる意図と目的のため、スコープは OAuth の前には存在しませんでした。OAuth が考案された典型的な使用事例は簡単なシナリオで、すでに嫌になるほど提示されてきました。LinkedIn は Gmail のパスワードを求めるので、Gmail にログインするときに使用し、プログラムによって連絡先(API を介して/スクラップなど)を獲得し、それ自体を広めますが、Gmail のパスワード情報は LinkedIn にそれ(ユーザーに代わってメールを送信する、メッセージを読み取るなど)以上のことを許可し、秘密の資格情報を危険にさらします。

OAuth は必要なアクションだけを Gmail に求め、Gmail の資格情報を LinkedIn とまったく共有せずに可能にするという LinkedIn のメカニズムを考案します。それだけです。スコープは LinkedIn が Gmail の連絡先(ほかのすべては例外)に委任されたアクセスをリクエストできるように OAuth2 が導入し、たたまたまのプリミティブです。意図が明確になり、使命が達成されましたね。

私にとってはこれで論文を実証するには十分です(スコープは委託のシナリオのみに関与し、ユーザーに代わってアプリができることをいつも制限します。委託されたユーザーがすでに所有する特権以外にアプリケーションのアクセス許可を付与するものではありませんが、私はこのシナリオだけで皆さんを説き伏せるには十分ではないことを承知しています。一般的な反論には、「それが本来の意図だったかもしれないが、他のシナリオにも使って成功するということにはならない!」 というものがあります。分かりました。予期しない問題をさらに掘り下げて、調査していきましょう。

「スコープは委託のシナリオのみに関与し、アプリがユーザーに代わってできることをいつも制限します。」

クレームとしてのスコープ

ここまで説明してきたことはアクセス許可を求めるために使用するスコープについてです。 OAuth2 ではアクセストークンでクレームされるスコープについは何も言っていません。これはアクセストークン(以下、「AT」)はクレームが必要ないという前提です(OAuth と OpenId Connect によると AT は形がなく、id_tokens だけが JWT として定義されています)。言うまでもなく、スコープは委任されたシナリオ以上の認証を表すために使用できるという意見は、スコープはクレームのフォームでアクセストークンをトラベルするという前提で深く定着しています。さらに具体的には、この問題の本質は アクセストークンにあるスコープ要求の存在はその対応するアクセス許可でベアラーを運用し、当該トークンで呼び出されたリソース(API など)はその後のチェックなしで対応するアクセスレベルを付与する という確信にあります。一般事例で表示するのはとても簡単ですが、その論理は通りません。

反事実的条件を設定するには、スコープクレームを運ぶ特定フォーマット(JWT)にある AT で使用している表記で遊んでみましょう。本書での命題の必然的結果は、一般的な場合では、アクセストークンのスコープだけでは認証判断をするには十分ではない、というものです。私のアイディアを出し、それが本当だという理由を 2 つ説明しましょう。1) OAuth レベルで見えるリソースは通常、認証を必要とする本当のリソースではない、2) 認証サーバーは博識でない。例を使ってこれを説明しましょう。Microsoft Graph はこの現象の非常に良い例なので Azure AD を使います。

OAuth2 リソースは本当のリソースでない

現在、ユーザーにサインインされた電子メール メッセージをすべてリストするために、Web アプリが API を呼び出します。Microsoft Graph を呼び出すために AT を要求し、スコープ Mail.Read を要求します。それを取得したら、Web アプリが me/messages を呼び出し、電子メールのリストを取得します。これで大丈夫です。では、呼び出し構文に少し変更を加えましょう。Me/messages の代わりに、 users/guid/messages を呼び出します。まだ動作します。素晴らしい!予定通りですね。

さて、思わぬ展開です。その呼び出しを繰り返しますが、今回は別のユーザーのメッセージを求めます。この要求の結果が 400 になるのを見て驚く方はいないと思います。この AT はまだ Mail.Read スコープを運んでいました!ここで重要なことは スコープは、ユーザー A に代わってアプリができることを認証サーバー (AS) に伝えるだけです。つまり、アプリはユーザー A のようにメッセージをリストできますが、 Mail.Read スコープの存在にかかわらず、ユーザー B のためにメッセージをリストできません。つまり、 Mail.ReadMail はリソースファミリのリソースタイプで、インボックスの特定のインスタンスではありません。しかし、アクセス時には、要求されている特定のインボックスが重要です。直感的ですね?API 行動が少しでも違っていれば、重大な問題になっていました。図 1 はシナリオの視覚的な要約を提供しています。

OAuth2 Scopes Mail API exmaple

図1:スコープ Mail.Read を運ぶトークンは、アプリはユーザーのメールボックスへ委託されたアクセスを得ることができるということを示します。そのスコープはユーザーが持っていないアプリの特権を付与しないので、別のユーザーのメールボックスにアクセスしようとしても失敗します。

これは例 1) です。リソースから情報にアクセスできるトークンを求めており、そのリソースは実際は、ファサードの API で、複数の詳細なリソースです。これら複数のソースにはライフサイクル、アクセス許可、特権、認証ルールがあります。このシナリオのスコープは非常に細かいものを表すのではなく、「現実に根差した」リソースです。アクセスする本当のリソースは各ユーザーのメールボックスで、情報にアクセス権を与えるのはトークンではなく REST の呼び出しによるものです。この操作ができるようにスコープの Mail.Read (これでプロセスを始める)、ユーザーの ID(これはトークンでは異なったクレームにある)、情報を要求するエンティティ(エンティティの URL にあり、実際の呼び出し)の 3 つを合わせます。

しかし、ソリューションは簡単です!OAuth2 レベルでサブレベルのリソースを述べましょう。プロトコルの真のリソースを表に出して、スコープがついに実際の特権を表せるようにします。表に出すリソースが超簡単であれば、可能なときもあります。つまり、通常、API を除いて存在しないということです。「良い考えを抱く」という点では、まだ多少反対です。コンテキストがないディレクティブを表すことができるケースがあったとしても、前述のケースがなくなるとは限りません。プログラミングが、スコープにコンテキストがないと想定する場合(さらに重要なのは、ユーザーがそれを理解している場合)、従来の委任されたスコープを得た時、アクセスチェックは制限が高くなり、コンテキストのチェックなしでディレクティブとして入るスコープでアクセス許可を入手します(メールボックにあることを確認してください)。

しかし、これは付与されたアクセス許可を表すスコープを使用する最大の問題ではありません(委任されたものとは対照的に)。最大の課題は、アクセス許可はいつもリソース固有だという事実に由来し、たくさんの複雑さを AS や発行するトークンに転送します。通常よりもたくさんのトークンリクエストが必要になるリソースがたくさんあります。トークンのライフサイクルは集中管理されておらず、非常に困難なこともあるので、AS が割り当てられたアクセス許可が十分であるか(またはそのアクセス許可について適時に学ぶ)を認識するのは難しいです。また、リソースに対してアクセス許可が多かったり、冗長になったりし、そうなるとトークンは非常に大きくなります。

さらに具体的には、アクセスされるリソースがあるリポジトリにあるドキュメントの場合を考えてください。上記で説明した素朴な見方によると、汎用 API がレポジトリのドキュメントにアクセスするよりもドキュメント自体を直接トークン要求に表すだけで、スコープを委任されたヒント(アクセス制御ロジックに反映された有効なアクセス許可を入手するように要求されている低レベルリソースでの実際のユーザーアクセル特権で、リソースがスコープの委託されたアクセス許可を交差することを要求します)から実際に割り当てられた特権(スコープの完全な存在がどこもチェックすることなく、アクセスを付与するのに必要なリソースです)にすることができます。このシナリオは図 2 に表示されています。

OAuth2 Scopes assigned privilege example

図 2:プロトコルレベルで、アクセスされている実際のリソースを明るみに出すことは可能です。また、Web API がアクセスポリシーを管理する必要性から軽減することも可能で、着信トークンに含まれるクレーム全体に依存しています。ただし、このアプローチのマイナス面は全知全能の AS が必要で、図 1 のアプローチで必要以上のトークンが必要になります。

注:AS への要求で表すために使うメカニズムは今回の話し合いの目的では重要ではありません。実際のシステムでは、使いやすさという点では大きな違いになりますが、たとえば、丁度欲するドキュメントにポイントするためにスコープ文字列にプレフィックスを加え、続いて要求するアクション(例::read)に従います。

アプローチは AS がすべてのリソースを表示し、誰が何にアクセスし、どの期間アクセスするかを管理するポリシーすべてを表示することが前提の条件です。これは図2に格納されているアクセスポリシーで表します。この知識を武器に、AS は即座に、要求元が要求されているアクセス許可に対応する特権があるかどうかを評価し、A) 要求が満たされない場合はトークンの発行を拒否する、または B) アクセスポリシーが評価されたというステートメントを含むトークンを発行し、そのユーザーは要求した操作を実行する許可が与えられます。B) の場合、AS を信頼するすべてのリソース(そして、OIDC または OAuth のどちらにも記されていない関数の AS にアクセス管理ポリシーを完全に委任したすべてのリソース)はトークンに含まれている認証ディレクティブに従います。

ここでは、このようなステートメントがスコープ要求で移動する理由がないことを少しの間、無視しましょう。異なるシナリオ用のエンティティをオーバーロードする必要がないどんなクレームタイプでも選択して、スコープが使用するロジックをさらに複雑にします。使用されるクレームに関係なく、異なるドキュメントの異なるトークンを要求することはトラフックをかなり増大させます。中規模ビジネスであっても、数百万のドキュメントがレポジトリにあることは珍しくありません。簡単なサムネイル ジェネレーターで生成されるトラフィックが、会社のアプリポートフォリオ中で同時に実行するユーザーで t 増殖し、すべてがその下に波及します(例:オフライン使用のトークン キャッシュのサイズを増やすと、それらキャッシュを使用する参照タイムも増えるなど)。

いくつの特権をトークンに送信したいか、という問題もあります。読み取りと書き込み操作のために異なるトークンのクライアント要求になることは可能ですが、 それはトークン展開の問題を悪化させ、トークン自体が非常に大きくなる可能性があるので、ユーザーが既知のドキュメントに対して持つすべての特権をバッチ処理することも問題になります。参照でクレームを表すことは常に可能ですが、クレームを評価するために API コールをすることはあらゆることの認証のために、シンプルで、即座に消費可能な真のソースとしてスコープを持つ目的を多少無効化ます。

認証サーバーは(普通)全知全能でない

最後に、あらゆることの最大の問題はドキュメントが作成、破棄されたとき、またはさまざまな容量をユーザーに割り当てられたときにいつも AS を輪に入れることは驚異的な作業で、クラウドや、パブリックネットワークを基にした分散システムよりもずっとシンプルな設定でさえもできません。

ファイルへのアクセスが Unix や Windows でどのように規制されているか考えてみてください。ドメイン コントローラーにはすべてのファイルのアクセス制御リストがあるわけではありません。各ファイルにはアクセス制御の独自のポリシーがあり、ファイルへのアクセス要求があると、その要求はアクセス要求が作成されたときではなく、その時点で評価されます。あらゆることを単純化する条件であったとしても(同種のリソースタイプ、ドメイン コントローラーによる一定の視線、安定したネットワーク操作など)、OSes がリソース側のポリシーに依存しなければならないようなはるかに困難なパブリック クラウドのシナリオの中で、どうすれば定期的な使用を想定できるでしょうか?

技術的に見て、これらすべてが可能です。「AS」の「A」は「authorization(認証)」で、すべての認証ロジックは AS で集中管理され、全ての認証決定はスコープの値で表現するという無邪気な想定とはずっと異なります。

まとめ

一般的なケースでは、スコープは仮定のユーザーに代わってアプリケーションができることを表すために使用し、 ユーザーが最初にできることのサブセットです。つまり、スコープは委託シナリオを処理するために使用します。リソースは着信スコープと実際のユーザー特権を組み合わせる担当をします。呼び出しの有効なアクセス許可を計算し、それに応じてアクセス制御の決定をします。

一般的なケースでは、アプリに割り当てられた実際の特権を表すものをたくさんスコープに入れすぎると問題があります(上記の委任されたアクセス許可とは反対に)。これは AS が実質的に達成・維持が可能でない詳細レベルで特殊の認証知識があることを必要とし、それができたとしても、トラフィックの増加やトークンサイズの増加のため、現実的ではありません。

AS がデータへのアクセス権があり、トークン発行時に認証評価が可能になるロジックがあれば、AS は認証決定を行うべきではない、というつもりはありません。 これらは発行を保留する決定や発行するトークンのコンテンツにももちろん、反映できますが、後者でスコープ要求に表す理由はなく、そうすることで都市伝説を存続させることにつながり、私のように長時間フライトの半分の時間を費やしてこの長い投稿を書くことになります。