【図解】OAuthのスコープとは?アプリに渡す権限の範囲を、Microsoft Graphの例と最小権限の考え方で初心者向けに解説

OAuth 2.0 の記事と認可コードフローの記事で、アクセストークンは「範囲と期限のある通行証」だと書きました。この「範囲」にあたるのがスコープです。scope= のパラメータや、同意画面に並ぶ許可の一覧、トークンの中に入る scp(スコープを表す項目)など、スコープはあちこちに顔を出します。この記事では、スコープがどこで決まり、誰が見て判断しているのかを、RFC と Microsoft のドキュメントで確かめながら図にしました。

この記事でわかること
  1. スコープは「このアクセストークンで何をしてよいか」の範囲
  2. スコープの書き方(RFC の決まりと、Microsoft の「リソース+許可の名前」)
  3. 頼んだ範囲がそのまま通るとは限らないこと、トークンは API ごとに1枚であること
  4. API はトークンの中のスコープの情報(scp・roles)を見て判断し、足りなければ 403 を返すこと
  5. スコープは最小限に、必要になったときに少しずつ頼むこと

まずはたとえで:開けられる扉の一覧

Microsoft Graph のような API は、「メールを読む」「メールを送る」「予定表を読む」のように、できることを細かく分けています。アクセストークンには、そのうちどの扉を開けてよいかの一覧が付いています。この一覧がスコープです。

API(Microsoft Graph など)できることが細かく分かれているメールを読む開くメールを送る開かない予定表を読む開くファイルを書く開かないアプリアクセストークンスコープ(範囲)・メールを読む・予定表を読むスコープは、トークンで「どの扉を開けてよいか」を決める一覧
図1 スコープは「開けてよい扉」の一覧

RFC 6749 も、クライアントは認可エンドポイントやトークンエンドポイントで、アクセスの範囲(scope)を指定できると書いています。Microsoft のドキュメントでは、API が許可を細かく分けておくことで、アプリは自分に必要な許可だけを頼めるようになり、利用者や管理者も「このアプリが何にアクセスできるか」を分かったうえで許可できる、と説明しています。OAuth 2.0 ではこの許可を「スコープ」と呼び、Microsoft では「アクセス許可(permission)」と呼ぶことも多いです。

スコープはどこに出てくるか

スコープは、アプリが頼んでから API が判断するまで、形を変えながら流れていきます。

人利用者アプリクライアント認可サーバEntra ID などAPIリソースサーバ① scope=… で頼む認可リクエストのパラメータ② 同意画面に、求めている許可の一覧が出る(スコープの文字列ではなく、説明文で)③ トークンの応答の scope実際に許された範囲④ トークンの中にもscp / roles として入るAuthorization: Bearer <トークン>⑤ scp / roles を見てこの操作をしてよいか判断足りなければ 403(insufficient_scope)スコープは「頼む → 同意 → 許された範囲 → トークンの中 → API の判断」と流れていく
図2 スコープが出てくる場所
場所スコープの姿
① 認可リクエストアプリが scope= のパラメータで「この範囲を許してほしい」と頼む
② 同意画面求めている許可が一覧で出る。スコープの文字列そのものではなく、説明文で表示される
③ トークンの応答scope の項目で、実際に許された範囲が返る
④ トークンの中Microsoft の場合、人の代わりなら scp、アプリ自身なら roles という項目(クレーム)として入る。くわしくはこの節の最後で説明する
⑤ APIトークンの scp や roles を見て、その操作をしてよいかを判断する

①と③は、認可コードフローの記事で見た Entra ID の例にも出てきます。

① 認可リクエストの scope(Microsoft のドキュメントより)

// Line breaks for legibility only

https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
&state=12345
&code_challenge=YTFjNjI1OWYzMzA3MTI4ZDY2Njg5M2RkNmVjNDE5YmEyZGRhOGYyM2IzNjdmZWFhMTQ1ODg3NDcxY2Nl
&code_challenge_method=S256

③ トークンの応答の scope(Microsoft のドキュメントより)

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
    "token_type": "Bearer",
    "expires_in": 3599,
    "scope": "https%3A%2F%2Fgraph.microsoft.com%2Fmail.read",
    "refresh_token": "AwABAAAAvPM1KaPlrEqdFSBzjqfTGAMxZGUTdM0t4B4...",
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIyZDRkMTFhMi1mODE0LTQ2YTctOD..."
}

どちらも URL エンコードされているので読みにくいですが、元に戻すと https://graph.microsoft.com/mail.read、つまり「Microsoft Graph でメールを読む」という意味です。

トークンの中の scp と roles とは

ここまでに何度か出てきた scp と roles は、アクセストークンの中に入っている項目の名前です。トークンは、「宛先はどの API か」「誰のトークンか」「何が許されたか」といった情報をいくつも持っています。Microsoft のドキュメントでは、この1つ1つの情報をクレームと呼んでいます。

人の代わりのトークン(委任)aud宛先の APItid ・ oid誰のトークンかscpMail.Read User.Readスペース区切りで、許されたスコープが並ぶアプリ自身のトークンaud宛先の APIoidどのアプリかroles[“User.Read.All”]許されたアプリロールが一覧で並ぶトークンの中の1つ1つの情報が「クレーム」。scp はスコープ、roles はアプリロールのクレーム(値は説明のための例。実際のトークンにはほかにも多くのクレームが入る)
図3 アクセストークンの中の scp と roles(イメージ)
  • scp:スコープ(scope)のクレームです。人の代わりに API を呼ぶ(委任の)トークンに入り、アプリが頼んで同意を得たスコープが、Mail.Read User.Read のようにスペース区切りで並びます。
  • roles:アプリロールのクレームです。人がいないクライアントクレデンシャルでは、スコープの代わりにこちらを使います。アプリに与えられたアプリケーションのアクセス許可が、一覧で入ります。アプリ自身のトークンには scp は入りません。

API は、受け取ったトークンのこの2つのクレームを見て、「このトークンでこの操作をしてよいか」を判断します。図3の値は説明のための例で、実際のトークンにはほかにも多くのクレームが入っています。なお、OAuth 2.0 の記事で書いたとおり、トークンの中身を読んで確かめるのは API の役目で、アプリはトークンを読まずにそのまま運びます。

スコープの書き方

RFC 6749 の決まりscope=openidMail.ReadCalendars.Readスペースで区切る・大文字と小文字は区別・並び順は関係ない・意味は認可サーバが決めるMicrosoft での書き方https://graph.microsoft.com/Calendars.Readリソース(API)の識別子許可の名前= Graph で予定表を読むGraph だけは識別子を省ける自社の API の場合api://<クライアント ID>/api.readアプリ ID URI + スコープ名名前の付け方の慣例Files.Read.All対象.操作.制約(resource.operation.constraint)
図4 スコープの書き方

RFC 6749 の決まり

RFC 6749 が決めているのは、書き方の枠組みだけです。

  • スペースで区切って並べる:スコープの値は、スペースで区切った文字列の並びです。並び順は関係ありません。
  • 大文字と小文字を区別する:同じつづりでも、大文字と小文字が違えば別の値です。
  • 意味は認可サーバが決める:どんな文字列があり、何を許すのかは、認可サーバ(とその先の API)が決めます。OAuth 2.0 の仕様が「メールを読む」のような中身を決めているわけではありません。

なお、RFC 6750 は、API がエラーで返すスコープの値について、プログラムが使うためのもので、利用者に見せるためのものではないと書いています。同意画面にスコープの文字列ではなく説明文が出るのも、このためです。

Microsoft での書き方

ここからは Microsoft の場合です。Microsoft では、スコープをリソース(API)の識別子と許可の名前をつなげて書きます。

スコープの例意味
https://graph.microsoft.com/Calendars.ReadMicrosoft Graph で、利用者の予定表を読む
User.Readリソースを省くと Microsoft Graph と見なされる。https://graph.microsoft.com/User.Read と同じ
api://<クライアント ID>/api.read自社の API の例。アプリ ID URI と、API の登録で決めたスコープ名をつなげる
openid・email・profileOpenID Connect のスコープ。ID トークンや、メールアドレス・名前などの情報を受け取る
offline_accessリフレッシュトークンを受け取る。同意画面では「アクセスを許可したデータへのアクセスを維持する」という意味の項目として出る

許可の名前には、Files.Read.All のように「対象.操作.制約」の形で付ける慣例があります。名前を見れば、何に対して何をする許可なのか、おおよそ見当がつきます。

スコープの名前と書き方は、API の持ち主が決める

ここまでで分かるとおり、規格が決めているのは書き方の枠組みだけです。「メールを読む」を Mail.Read と書くか、別の名前にするか、リソースの識別子を付けるかどうかは、規格の外の話です。スコープの名前と書き方を決めているのは、その API(リソース)の持ち主です。

OAuth 2.0(RFC 6749)書き方の枠組み決めることスペース区切り・大文字小文字を区別・意味は認可サーバが決めるOpenID Connect共通のスコープ決めることopenid・profile・email・offline_access などAPI の持ち主名前と書き方決めることGraph:https://graph.microsoft.com/User.Read自社 API:api://<ID>/orders.readアプリの開発者API のドキュメントで調べて、コードや設定に書く同じ Microsoft でも、API によって書き方が違うことがある
図5 スコープの、誰が何を決めるか
  • OAuth 2.0(RFC 6749):スペースで区切る、大文字と小文字を区別する、意味は認可サーバが決める、という枠組みだけを決めています。
  • OpenID Connect:openid・profile・email・offline_access などは OpenID Connect の仕様で決まっているので、認可サーバが違っても共通です。ただし、どこまで対応しているかは認可サーバによります(Microsoft は address と phone には対応していません)。
  • API の持ち主:それ以外のスコープの名前と書き方を決めます。同じ Microsoft の中でも、API によって違うことがあります。Microsoft の MSAL のドキュメントも、スコープの書き方はトークンを受け取る API と、その API が受け付ける宛先(aud)によって変わると書いています。
スコープの例(Microsoft のドキュメントより)書き方の特徴
https://graph.microsoft.com/User.ReadMicrosoft Graph。User.Read だけでも同じ意味になる
https://management.core.windows.net//user_impersonationAzure Resource Manager。宛先の末尾にスラッシュが付くため、スラッシュを2つ重ねる
00001111-aaaa-2222-bbbb-3333cccc4444/api.readhttps:// などを付けず、アプリの ID とスコープ名だけを求める API もある

そのため、アプリの開発者は、呼びたい API のドキュメント(Microsoft Graph ならアクセス許可の一覧)でスコープの名前と書き方を調べ、それをアプリのコードや設定に書きます。Microsoft の場合は、使う許可をアプリ登録にも登録しておきます。

逆に、自社の API を Entra ID で守るときは、自分がスコープを決めて公開する側になります。Expose an API でアプリ ID URI とスコープ名を登録し、その名前を API を使うアプリの開発者に伝え、API のコードでトークンの scp を見て判断します。

頼んだ範囲がそのまま通るとは限らない

アプリが頼んだスコープは、あくまで「希望」です。最終的にどこまで許すかは、認可サーバが決めます。

アプリ頼んだ範囲ABC認可サーバ・認可サーバの方針・持ち主(利用者)の判断・管理者の設定で、全部または一部を外せるトークン範囲A・C応答の scope で知らせる頼んだ範囲と違うときは必ず返す(RFC 6749)アプリは、応答の scope を見て「実際に何が許されたか」を確かめる
図6 認可サーバが範囲を絞り、結果を応答の scope で知らせる
  • 一部または全部を外せる:RFC 6749 は、認可サーバが、自分の方針や持ち主の判断によって、頼まれたスコープの全部または一部を無視できるとしています。
  • 違うときは必ず知らせる:出したスコープが頼まれたものと違うときは、トークンの応答に scope を入れて、実際に許した範囲を知らせなければなりません。
  • 省いたとき:アプリがスコープを指定しなかったときは、あらかじめ決めた既定の範囲で進めるか、エラーにするかのどちらかです。

Microsoft の場合、強い権限のスコープは管理者しか同意できません。たとえば User.Read.All(全ユーザーのプロフィールを読む)、Directory.ReadWrite.All(ディレクトリに書き込む)、Group.Read.All(全グループを読む)です。組織のユーザーがこれらを頼むと、同意できない旨のエラーになります。どのスコープに管理者の同意が要るかは、API を作った側が決めるほか、テナントの管理者が設定で厳しくすることもできます。そのため、アプリはトークンが取れない場合にも備えておく必要があります。

トークンは API ごとに1枚

Microsoft の場合、1つのアクセストークンで呼べるのは1つの API だけです。では、Microsoft Graph と自社の API の両方を呼ぶアプリは、どうするのでしょうか。流れを並べると、まとめて1回で済む部分と、API ごとに別々になる部分に分かれます。

認可サーバ人ブラウザアプリ認可EPトークンEPGraph自社 APIまとめて1回(同意)API ごと(Graph 用)API ごと(自社 API 用)① 案内② /authorizeMail.Read + api.read + offline_access③ 1つの同意画面で、両方を許可④ 認可コード⑤ コード + Graph の範囲⑥ トークン1(宛先:Graph)⑦ トークン1 で Graph を呼ぶ応答⑧ リフレッシュトークン + 自社 API の範囲同意済みなので画面は出ない⑨ トークン2(宛先:自社 API)⑩ トークン2 で自社 API を呼ぶ応答同意は1回にまとめられる。トークンの取得と API の呼び出しは、API ごとに別々
図7 複数の API を呼ぶときの流れ:同意はまとめて1回、トークンは API ごと
部分手順何が起きるか
まとめて1回(同意)①〜④/authorize では、複数の API のスコープを1回の要求に並べられる。利用者は1つの同意画面で、Graph の Mail.Read と自社 API の api.read の両方を許可できる
API ごと(Graph 用)⑤〜⑦/token で1回に頼めるスコープは、1つの API のものだけ。まず Graph の範囲でトークン1を取り、Graph を呼ぶ
API ごと(自社 API 用)⑧〜⑩自社 API の範囲で、もう一度 /token を呼んでトークン2を取り、自社 API を呼ぶ

⑧で2枚目のトークンを取るときは、①〜③をやり直す必要はありません。①で offline_access を頼んでおくとリフレッシュトークンが届くので、それを使ってトークンエンドポイントに頼めます。Microsoft のドキュメントも、リフレッシュトークンは同意済みのすべての許可に使え、たとえば mail.read の要求で受け取ったリフレッシュトークンで、別の API のトークンを頼めると説明しています。自社 API の範囲は③で同意済みなので、画面も出ません。MSAL を使う場合は、この2枚目の取得をライブラリが画面を出さずに行ってくれます。

トークンには、宛先の API(aud)と、その API で許された範囲(scp)が入ります。トークン1は宛先が Graph なので、自社の API に見せても受け付けてもらえません。複数の API を呼ぶアプリは、API ごとにトークンを取り分け、呼ぶ API に合ったトークンを付けて送ります。

API の側:スコープを見て判断する

スコープを最後に判断するのは、トークンを受け取る API(リソースサーバ)です。ここでは例として、アプリが Microsoft Graph からメールの一覧を読む場面で、流れを追ってみます。Graph を呼ぶ場合、図8の③〜⑤の確認は、Microsoft が Graph の中で行います。自社の API を作る場合は、同じ確認を自分の API に組み込みます。Microsoft のドキュメントも、API はトークンの中のスコープが自分の公開しているものか確かめ、その値で認可の判断をするよう書いています。

人利用者アプリAPI(リソースサーバ)例:Microsoft Graph確認は API の中で行うすべて OK のときだめなとき①「メールを見る」② GET /me/messagesAuthorization: Bearer <トークン>③ トークンは正しいか署名・発行元・宛先(aud)・期限④ 範囲は足りるかscp に Mail.Read があるか(アプリ自身なら roles)⑤ その人に権限はあるか委任では、その人ができる範囲まで⑥ 200 + メールの一覧⑦ 画面に表示401(③がだめ)トークンを取り直す403(④⑤がだめ)同意を頼む・使えないと表示200 も 401・403 も、返る先はアプリ。判断するのは API の側
図8 Microsoft Graph を呼んだときの流れ:確認は API の中で行い、結果はアプリに返る
手順何が起きるか
①②利用者の操作をきっかけに、アプリがトークンを Authorization: Bearer ヘッダーに付けて Graph を呼ぶ
③Graph が、トークンそのものが正しいかを確かめる(署名、発行元、宛先が Graph か、期限)。だめなら 401
④Graph が、この操作に要る範囲がトークンにあるかを確かめる。人の代わりなら scp、アプリ自身なら roles を見る。足りなければ 403
⑤人の代わりに呼ぶときは、アプリに許された範囲に加えて、サインインしているその人の権限も見る。その人ができないことなら 403
⑥⑦200 とメールの一覧は、アプリに返る。利用者が目にするのは、アプリがそれを画面に表示したもの

200 で応じる相手は、利用者ではなくアプリです。401 や 403 も同じようにアプリに返るので、アプリはそれぞれの場合の動きを用意しておきます。たとえば、401 ならトークンを取り直し、403 なら足りない範囲の同意を頼むか、その機能が使えないことを画面に出します。

クレーム入る場面中身
scp人の代わりに呼ぶとき(委任)アプリが頼み、同意を得たスコープ。スペース区切りの文字列。アプリ自身として呼ぶトークンには入らない
rolesアプリ自身として呼ぶとき(クライアントクレデンシャル)アプリに与えられたアプリケーションのアクセス許可(アプリロール)の一覧

範囲が足りないときの返し方も、RFC 6750 で決まっています。

エラー状態意味(RFC 6750)
invalid_token401トークンの期限切れ、取り消し、形が正しくないなど
insufficient_scope403トークンの範囲より高い権限が必要。必要なスコープを応答に入れてもよい

「トークンそのものがだめ」なら 401、「トークンは正しいが範囲が足りない」なら 403、と分かれています。トラブルのときに、どちらが返っているかを見ると切り分けやすくなります。Graph の場合、エラーの詳しい理由は、応答の本文(JSON)にも入って返ってきます。また、図8の⑤のとおり、委任ではスコープがあっても、その人自身ができないことはできません。スコープは「アプリに任せた範囲」で、「その人の権限」を超えるものではないからです。

スコープは最小限に

OAuth 2.0 のセキュリティのベストプラクティス(RFC 9700)は、アクセストークンの権限を、そのアプリや使い方に必要な最小限に絞るよう求めています。Microsoft も、開発者は最小権限の原則を守り、アプリが動くのに必要な許可だけを頼むよう書いています。

必要になったときに少しずつ頼む(段階的な同意)

Microsoft では、すべてのスコープを最初にまとめて頼む必要はありません。最初は最低限のスコープだけを頼み、利用者が機能を使い始めたときに、必要なスコープを追加で頼めます。

時間サインインしたときそのとき同意を頼むUser.Readプロフィールを見るそのあとのトークンUser.Read予定表の機能を使ったときそのとき同意を頼むCalendars.Read予定表を読むそのあとのトークンUser.ReadCalendars.Readメール送信を使ったときそのとき同意を頼むMail.Sendメールを送るそのあとのトークンUser.ReadCalendars.ReadMail.Send必要になったときに少しずつ頼む(段階的な同意)。トークンには、それまでに許された範囲が入る
図9 必要になったときに少しずつ頼む(段階的な同意)

新しく頼んだスコープは、それまでに許されたスコープに足されます。トークンには、それまでにアプリに許されたスコープがまとめて入ります。Microsoft は、最初からたくさんの許可を頼むと利用者や管理者が許可をためらいやすいので、基本の機能に要るものから頼み、機能に合わせて足していくのがよいとしています。なお、段階的な同意が使えるのは、委任のアクセス許可だけです。

.default:アプリ登録に書いた許可をまとめて頼む

https://graph.microsoft.com/.default のように、API の識別子に /.default を付けると、アプリ登録にあらかじめ書いておいた許可をまとめて頼む意味になります。

アプリEntra ID(認可サーバ)① アプリ登録に書いた許可Microsoft GraphUser.ReadMail.ReadCalendars.Read② scope に .defaulthttps://graph.microsoft.com/.default③ 一覧を見る④ まだ同意がなければ一覧ぜんぶを1つの同意画面で⑤ トークン(宛先:Graph)scp:User.Read Mail.Read   Calendars.Read同意済みの範囲がまとめて入る⑥ アプリへ1つずつ書いて頼むscope=…/User.Read …/Mail.Read頼む範囲:コードに書いたもの.default で頼むscope=…/.default頼む範囲:アプリ登録の一覧ぜんぶクライアントクレデンシャル必ず .default(トークンには roles)混ぜられない…/.default と Mail.Read を一緒に → エラー.default は「アプリ登録の一覧をまとめて頼む」合図
図10 .default は、アプリ登録に書いた許可の一覧をまとめて頼む
場面.default の扱い
クライアントクレデンシャル必ず .default を使う。アプリケーションのアクセス許可は、1つずつ指定して頼めない
人の代わり(委任)まだ同意がなければ、アプリ登録に書いた許可の同意画面が出る。同意済みなら、許された範囲がまとめてトークンに入る
組み合わせ.default と個別のスコープを1回の要求で混ぜることはできない(エラーになる)

Microsoft は、管理者の同意が要る許可は、アプリ登録の画面に一覧として書いておくよう勧めています。そうすれば、管理者が組織全員の分をまとめて同意しやすくなります。

注意点

注意点理由と対策
念のため広く頼まない範囲が広いほど、トークンが漏れたときの影響が大きい。.All の付く許可は、本当に全体が必要なときだけにする
判断は API でするアプリの画面でボタンを隠しても、トークンさえあれば API は呼べる。範囲の確認は、API が scp や roles を見て行う
応答の scope を確かめる頼んだ範囲がそのまま許されるとは限らない。足りない機能は使えないことを、アプリが利用者に伝えられるようにする
401 と 403 を見分ける401 はトークンそのものの問題、403(insufficient_scope)は範囲の不足。対処が違う

企業での例

場面頼むスコープの例押さえどころ
社内ポータルで、本人の予定だけを表示するUser.Read、Calendars.Read(委任)本人の予定を読むだけなので、書き込みや全員分の許可は頼まない
夜間に全社員のプロフィールを人事システムと突き合わせるhttps://graph.microsoft.com/.default(アプリケーション。中身は User.Read.All など)管理者の同意が必要。テナント全体に届くので、読む許可だけにとどめる
自社の API を「読む」と「書く」で分けて公開するapi://<ID>/orders.read、api://<ID>/orders.writeAPI の登録でスコープを分けておき、API のコードで scp を見て操作ごとに判断する

まとめ

項目内容
スコープとはアクセストークンで「何をしてよいか」の範囲。開けてよい扉の一覧
書き方RFC:スペース区切り、大文字小文字を区別、意味は認可サーバが決める。名前と書き方は API の持ち主が決め、アプリはドキュメントで調べて実装する。Microsoft:リソースの識別子+許可の名前(API によって違いあり)
決まり方アプリが頼み、利用者や管理者が同意し、認可サーバが最終的に決める。結果は応答の scope で返る
トークンAPI ごとに1枚。中には scp(委任)や roles(アプリ自身)として入る
API の判断トークンが正しくなければ 401、範囲が足りなければ 403(insufficient_scope)
頼み方最小限に。必要になったときに少しずつ頼む。クライアントクレデンシャルは .default

参考にした資料

この記事は、次の標準と公式ドキュメントで内容を確かめて書きました。仕様や対応状況は変わることがあるので、最新の情報はリンク先で確かめてください。

あとがき

スコープは文字列の一覧にすぎないのですが、追いかけてみると、頼む・同意する・絞られる・トークンに入る・API が見る、と何人もの手を渡っていました。特に「アプリの画面でボタンを隠しても守ったことにはならず、最後に判断するのは API」という点は、自社の API を作るときに忘れずにいたいところです。Entra ID のアプリ登録で .All の付いた許可を見かけたら、本当に全体が必要なのか、一度立ち止まって考えたいと思いました。

コメント