【図解】OAuth 2.0とは?パスワードを渡さずにアプリへ権限を任せるしくみを4つの役割とトークンで初心者向けに解説

このシリーズでは、API キーやセッションとトークン、API の認証方式を図にしてきました。その中で何度も出てきたのが「Bearer トークン」と OAuth 2.0 です。「Microsoft でサインイン」のボタンや、「このアプリに次のアクセス許可を与えますか」という画面の裏で動いているのも OAuth 2.0 です。この記事では細かい手順に入る前に、OAuth 2.0 が何を解決するしくみなのか、誰が登場して何を受け渡すのかを、RFC と Microsoft のドキュメントで確かめながら図にしました。

この記事でわかること
  1. OAuth 2.0 は、パスワードを渡さずに、アプリに「決まった範囲の操作」だけを任せるしくみ
  2. 登場人物は4つ:リソースオーナー・クライアント・認可サーバ・リソースサーバ
  3. アクセストークンは「範囲と期限のある通行証」
  4. トークンのもらい方(グラント)の種類と、今使われているもの
  5. OAuth 2.0 は「認可」のしくみで、サインイン(認証)は OpenID Connect が足す
  6. 動かす前に、アプリと API を認可サーバに登録しておくこと

まずは身近な例で:写真の印刷を頼む

OAuth 2.0 の仕様(RFC 6749)は、こんな例から説明を始めています。写真共有サイトに置いた写真を、別の会社の印刷サービスで印刷したい。印刷サービスは、写真共有サイトから写真を取り出す必要があります。

いちばん簡単なのは、写真共有サイトの ID とパスワードを印刷サービスに教えることです。でも、これにはいろいろな困りごとがあります。

パスワードを渡すOAuth 2.0:通行証を渡すあなたID とパスワード印刷サービス写真共有サイトあなたとして入れる× パスワードを保存される× 写真以外も何でもできてしまう× このサービスだけ取り消せない× 漏れるとパスワードが漏れるあなた写真サイトの許可の係許可する通行証(トークン)印刷サービス写真を読むだけ◯ パスワードは渡らない◯ できることは写真を読むだけ◯ 期限つき・このサービスだけ取り消せる
図1 パスワードを渡すやり方と、OAuth 2.0 のやり方

RFC 6749 は、パスワードを渡すやり方の問題として次のようなものを挙げています。右の列が、OAuth 2.0 でどう変わるかです。

パスワードを渡すとOAuth 2.0 では
印刷サービスが、あとで使うためにパスワードを保存しておくことになる印刷サービスにパスワードは渡らない。渡るのは通行証(トークン)だけ
印刷サービスが、写真以外のものまで広く触れてしまう通行証には「写真を読む」のような範囲が付く
印刷サービスだけ使えなくするには、パスワードを変えるしかなく、ほかのサービスも全部止まる通行証は、サービスごとに取り消せる
印刷サービスから漏れると、パスワードそのものが漏れる漏れるのは範囲と期限のある通行証で、パスワードではない

つまり OAuth 2.0 は、パスワードの代わりに、範囲と期限のある通行証をアプリに渡すしくみです。RFC 6749 も、クライアントはリソースオーナーの資格情報(パスワードなど)を使う代わりにアクセストークンを手に入れる、と書いています。

登場人物は4つ

OAuth 2.0 では、関係する人やサーバを4つの役割に分けます。写真の例に当てはめると、次のようになります。

リソースオーナーあなた(写真の持ち主)クライアント印刷サービス(アプリ)認可サーバ本人を確かめ、許可を受けて通行証(トークン)を出す写真サイトのログイン・許可の係リソースサーバ守られたデータを持ち、通行証を確かめて応じる写真サイトの APIサインインして「許可する」通行証を渡す通行証を見せてデータを頼む持ち主・アプリ・通行証を出す係・データの置き場所の4つに分けて考える
図2 OAuth 2.0 の4つの役割(写真の印刷の例)
役割意味(RFC 6749)写真の例Microsoft の場合
リソースオーナー守られたデータへのアクセスを許せる人。たいていは利用者本人あなたサインインするユーザー
クライアント持ち主の許可を受けて、持ち主の代わりにデータを頼むアプリ印刷サービスEntra ID に登録したアプリ
認可サーバ持ち主を確かめ、許可を受けて、クライアントにアクセストークンを出すサーバ写真サイトのログイン・許可の係Microsoft ID プラットフォーム(Entra ID)
リソースサーバ守られたデータを持ち、アクセストークンを確かめてリクエストに応じるサーバ写真サイトの APIMicrosoft Graph や、自社の Web API

「クライアント」という言葉に注意してください。OAuth 2.0 のクライアントは、人が使うパソコンではなく、データを取りに行くアプリのことです。Web サーバで動くアプリも、スマホのアプリも、別の API を呼ぶ API も、クライアントになります。

認可サーバとリソースサーバは、同じ会社のサービスのこともあれば、別々のこともあります。Microsoft の場合は、Entra ID がトークンを出し、Microsoft Graph などの API がそのトークンを確かめます。

全体の流れ

RFC 6749 は、4つの役割の間のやり取りを (A)〜(F) の6つの手順で説明しています。

クライアントアプリリソースオーナーあなた認可サーバ許可の係リソースサーバAPI(A) 許可を求める(B) 許可(グラント)を受け取る(C) 許可を見せてトークンを頼む(D) アクセストークンを出す(E) トークンを付けてデータを頼む(F) トークンを確かめて応じるクライアントの認証もここでトークンだけで判断。パスワードは知らない(A)(B) は、認可サーバの画面を通して行うのが一般的
図3 OAuth 2.0 の全体の流れ(RFC 6749 の (A)〜(F))
  1. (A)(B) クライアントが、持ち主に「写真を読んでいいですか」と許可を求め、許可の証(グラント)を受け取る。
  2. (C)(D) クライアントが、許可の証を認可サーバに見せてトークンを頼む。認可サーバは、クライアントを確かめ、許可の証が正しければアクセストークンを出す。
  3. (E)(F) クライアントが、アクセストークンを付けてリソースサーバにデータを頼む。リソースサーバはトークンを確かめ、正しければ応じる。

大事なのは、リソースサーバは持ち主のパスワードを知らないことです。トークンだけを見て判断します。また、(A)(B) は持ち主とクライアントが直接やり取りする形で描かれていますが、実際には認可サーバのサインイン画面と同意画面を通して行うのが一般的です。認可コードという方式では、持ち主は認可サーバにだけサインインするので、パスワードはクライアントに一切渡りません。

2つの窓口:認可エンドポイントとトークンエンドポイント

認可サーバは、トークンを出すだけの係ではありません。RFC 6749 は認可サーバを「持ち主を確かめ、許可をもらったうえで、クライアントにアクセストークンを出すサーバ」と定義しています。つまり、本人を確かめる、許可をもらう、アプリを確かめてトークンを出す、の3つの仕事をします。

この仕事を、認可サーバは2つの窓口(エンドポイント)に分けて受け持っています。分け方の決め手は、誰が来る窓口かです。

認可サーバEntra ID など認可エンドポイント/authorize・本人を確かめる(サインイン)・許可をもらう(同意)→ 許可の証(認可コード)を出すトークンエンドポイント/token・アプリを確かめる・許可の証をトークンに換える→ アクセストークンを出す人(ブラウザ)画面で操作ブラウザ経由アプリ(クライアント)直接やり取り画面は出ない人が来る窓口と、アプリが来る窓口に分かれている
図4 認可サーバの2つの窓口:人が来る窓口と、アプリが来る窓口
項目認可エンドポイントトークンエンドポイント
誰が来るか人(ブラウザ)。アプリがブラウザをここへ移動させる(リダイレクト)アプリ(クライアント)が直接呼ぶ
ここでする仕事本人を確かめる(サインイン)。アプリに許可するかを聞く(同意)アプリを確かめる。許可の証をアクセストークンに換える
出すもの許可の証(認可コード)アクセストークン(場合によりリフレッシュトークンも)
画面サインイン画面や同意画面が出る画面は出ない。プログラムどうしのやり取り

認可コードという方式で、実際にどちらの窓口がいつ使われるかを並べると次のようになります。前半はブラウザ経由で人が画面を操作し、後半はアプリが直接トークンエンドポイントとやり取りします。

認可サーバ人ブラウザアプリクライアント認可エンドポイントトークンエンドポイントAPIリソースサーバブラウザ経由(人が画面で操作する)① 窓口へ案内② サインイン画面を開く③ サインインして、同意画面で「許可」を押す④ 許可の証(認可コード)を付けて戻すアプリに届くアプリから直接(画面は出ない)⑤ 許可の証 + アプリ自身の証明⑥ アクセストークン⑦ トークンを付けて API を呼ぶ図3 の (A)(B) が①〜④、(C)(D) が⑤⑥、(E)(F) が⑦にあたる
図5 2つの窓口の使い分け(認可コードの場合)
  • ①〜④ 認可エンドポイント:アプリは人をサインイン画面へ案内するだけで、パスワードには触れません。人がサインインして「許可」を押すと、認可サーバは許可の証(認可コード)を付けてブラウザをアプリへ戻します。
  • ⑤⑥ トークンエンドポイント:アプリは受け取った許可の証を、自分自身の証明と一緒にトークンエンドポイントへ送り、アクセストークンに換えます。クライアントはふつう、ここで自分自身を確かめてもらいます。トークンはブラウザを通らずに、アプリに直接渡ります。
  • ⑦ アプリは受け取ったアクセストークンを付けて API を呼びます。

すべての方式が両方の窓口を使うわけではありません。人がいないクライアントクレデンシャルでは、サインインも同意の画面もないので、アプリはトークンエンドポイントだけを使います。アクセストークンの期限が切れたときに、リフレッシュトークンで取り直すのもトークンエンドポイントです。

Microsoft のドキュメントにある、Entra ID の2つのエンドポイントの例です。<issuer> には、テナント ID やドメイン名、common などが入ります。アプリ登録の Entra ID ›› App registrations ›› <アプリ> ›› Endpoints でも確かめられます。

Entra ID のエンドポイントの例(Microsoft のドキュメントより)

# Authorization endpoint - used by client to obtain authorization from the resource owner.
https://login.microsoftonline.com/<issuer>/oauth2/v2.0/authorize
# Token endpoint - used by client to exchange an authorization grant or refresh token for an access token.
https://login.microsoftonline.com/<issuer>/oauth2/v2.0/token

アクセストークン:範囲と期限のある通行証

RFC 6749 は、アクセストークンを「守られたデータにアクセスするための資格情報」で、「クライアントに出された許可を表す文字列」だと説明しています。トークンには、どの範囲を、いつまで許すかが結び付いています。

アクセストークン(通行証)例:写真サイトの API 用できること:写真を読む期限:発行から一定時間宛先:写真サイトの API持っている人なら誰でも使える(Bearer=持参人)範囲(scope)何をしてよいか。必要な分だけにする期限切れたら使えない。取り直す宛先(どの API 用か)決まった API だけで使えるようにするだから、HTTPS で運び、漏れないようにしまっておく
図6 アクセストークンは、範囲・期限・宛先のある通行証
  • 範囲(scope):何をしてよいか。クライアントは、認可エンドポイントやトークンエンドポイントで範囲を指定できます。
  • 期限:アクセストークンには期限があり、切れたら取り直します。取り直しに使うのがリフレッシュトークンで、Microsoft は、リフレッシュトークンを機密の情報として扱うよう書いています。
  • 宛先:OAuth 2.0 のセキュリティのベストプラクティス(RFC 9700)は、アクセストークンを特定のリソースサーバ向けに絞ることを勧めています。

「Bearer=持参人」のトークン

OAuth 2.0 でよく使われるのは Bearer トークンです。RFC 6750 は、Bearer トークンを「持っている人なら誰でも、ほかの持ち主と同じように使えるトークン」と定義しています。電車の切符のように、持っている人が使えるということです。

アクセストークンを付けて API を呼ぶ例(RFC 6750 より)

GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM

持っているだけで使えるということは、API の側からは「誰が持ってきたか」を区別できないということです。正しいアプリが持ってきても、写しを手に入れた誰かが持ってきても、トークンが正しければ同じように応じてしまいます。

正しいアプリAuthorization: Bearer <トークン>HTTPS の中を通るAPIリソースサーバ写しを手に入れた誰か同じトークンを付ける持ってきた相手は区別しないだから、こうして守るHTTPS で運ぶ途中で読まれないしまい方に注意ログや画面に出さない期限を短く漏れても長く使えない宛先と範囲を絞る使える場所を減らす
図7 Bearer トークンは、持ってきた相手を区別しない

そのため、漏れないようにすることが何より大事です。RFC 6750 は、Bearer トークンを付けたリクエストには必ず TLS(https)を使うよう求めています。図7の下のように、運び方・しまい方・期限・宛先と範囲の4つで守りを重ねます。さらに RFC 9700 は、トークンを特定の持ち主(送り手)に縛るしくみを使うことも勧めています。Microsoft の Entra ID が出すアクセストークンも Bearer で、形式は JWT です。

トークンの中身はリソースサーバが見るもの

アクセストークンは、アプリが受け取って API に渡しますが、中身を見るのはアプリではありません。Microsoft のドキュメントは、クライアントはアクセストークンを「中身の分からない文字列」として扱い、確かめようとしないこと、確かめるのはリソースサーバの役目であることを書いています。トークンの中身は、宛先の API のためのものだからです。

認可サーバトークンを作るトークン+期限・範囲の情報アプリ(クライアント)封をしたまま運ぶAuthorization: BearerAPI(リソースサーバ)封を開けて確かめる署名書き換えられていないか発行元信頼する認可サーバか宛先この API 向けか期限期限内か範囲・ロールその操作をしてよいかアプリは中身を読まない・確かめない期限や範囲は、トークンと一緒に返る情報で知るトークンの中身は、宛先の API のためのもの
図8 アプリは封をしたまま運び、API が封を開けて確かめる
  • アプリ(クライアント):トークンを封筒のように、封をしたまま運びます。期限や範囲は、トークンと一緒に返ってくる情報(expires_in など)で知ります。Microsoft のサービス向けのトークンは、JWT として読めない形式だったり、暗号化されていたりすることもあります。
  • API(リソースサーバ):届いたトークンの封を開けて確かめます。Microsoft の Web API 向けのライブラリは、宛先(この API 向けか)、発行元(信頼できる認可サーバか)、期限、署名(書き換えられていないか)を確かめ、そのうえで範囲(scp)やロール(roles)で、その操作をしてよいかを判断します。

トークンのもらい方(グラント)

RFC 6749 は、許可の証(グラント)のもらい方として4つの種類を決めています。ただし、そのあとに出たセキュリティのベストプラクティス(RFC 9700)で、2つは使わない扱いになりました。

認可コード主に使うクライアントクレデンシャル使うインプリシット使わない(SHOULD NOT)パスワード(ROPC)使ってはいけない(MUST NOT)人認可 EP許可アプリコードトークン EPトークン人はブラウザで許可。トークンはアプリへ直接アプリ(人はいない)トークン EP自分の証明トークンアプリ自身として。トークン EP だけを使う人認可 EPトークンブラウザに直接トークンがブラウザを通るので、漏れる経路がある人アプリパスワードトークン EPアプリがパスワードを受け取る=図1 の左に戻る
図9 4つのグラントの違い(EP はエンドポイント)
グラントどんなときに使うか今の扱い(RFC 9700)
認可コード人がサインインして許可し、アプリがその人の代わりに API を呼ぶ。持ち主は認可サーバにだけサインインするので、パスワードはアプリに渡らない主に使う。PKCE を組み合わせる(公開クライアントは必須、機密クライアントも推奨)
クライアントクレデンシャル人はいない。アプリが自分の管理する範囲のデータに、アプリ自身としてアクセスする使う
インプリシットブラウザのアプリが、認可エンドポイントから直接トークンを受け取る使わないことが推奨(SHOULD NOT)
リソースオーナーパスワードクレデンシャルアプリが利用者のパスワードを受け取り、それでトークンを取る使ってはいけない(MUST NOT)

パスワードを受け取るグラントが「使ってはいけない」になっているのは、図1の「パスワードを渡すやり方」に戻ってしまうからです。今は、人がいる場面では認可コード、人がいない場面ではクライアントクレデンシャル、と覚えておけば十分です。

人がサインインする?するしない認可コード + PKCE人の代わりに API を呼ぶ例:社員が使う Web アプリクライアントクレデンシャルアプリ自身として API を呼ぶ例:夜間のバッチ・自動化インプリシットとパスワードのグラントは選ばない
図10 どのグラントを使うか

人がサインインする場合(認可コード)の流れは、図5で見たとおりです。人がサインインしない場合(クライアントクレデンシャル)は、次のようにずっと短くなります。Microsoft の場合で並べます。

管理者事前の準備アプリ夜間バッチなど認可サーバEntra IDAPIMicrosoft Graph など⓪ アプリケーションの許可に同意しておくこのときだけ人が関わる実行のたび(人はいない)① トークン EP に POSTアプリ自身の証明 + 範囲② アプリを確かめ、同意済みの許可を入れたトークンを作る③ アクセストークン④ Authorization: Bearer <トークン>⑤ トークンを確かめ、アプリの許可で判断⑥ 応答(データ)サインイン画面も同意画面も出ない。認可エンドポイントは使わない
図11 人がサインインしないときの流れ(クライアントクレデンシャル、Microsoft の場合)
  • ⓪ 事前の準備:アプリケーションのアクセス許可は、管理者が同意しておきます。人が関わるのはここだけです。
  • ①〜③:アプリはトークンエンドポイントに、アプリ自身の証明(クライアントシークレットや証明書)と範囲を送ります。Microsoft Graph を呼ぶなら、範囲は https://graph.microsoft.com/.default です。.default は「管理者が同意したアプリの許可をまとめて入れてほしい」という意味です。
  • ④〜⑥:アプリはトークンを付けて API を呼びます。API はトークンを確かめ、アプリに付いた許可(roles)で判断します。

図5と比べると、ブラウザもサインイン画面も出てこず、認可エンドポイントも使いません。その代わり、アプリ自身の証明が人のサインインの代わりになるので、シークレットや証明書の管理が大事になります。Microsoft は、シークレットより証明書を使うことを勧めています。

人の代わりか、アプリ自身か

2つのグラントの違いは、Microsoft のドキュメントの「委任されたアクセス」と「アプリのみのアクセス」の説明で見るとわかりやすくなります。ここからは Microsoft の場合の話です。

人の代わりに(委任)サインインした人の代理アプリ自身としてサインインする人はいないユーザーアプリAPIユーザーと、アプリの許可の両方がそろったときだけ例:Files.Read.All(委任)その人が読めるファイルだけ読めるMicrosoft の名前:委任されたアクセス許可主なグラント:認可コードアプリ(夜間処理など)APIアプリに付けた許可だけで判断例:Files.Read.All(アプリケーション)テナントのどのファイルでも読めるMicrosoft の名前:アプリケーションのアクセス許可主なグラント:クライアントクレデンシャル
図12 人の代わりに使うか、アプリ自身として使うか(Microsoft の場合)
項目委任されたアクセス許可アプリケーションのアクセス許可
誰としてサインインした人の代わりアプリ自身(人はいない)
できる範囲その人ができないことは、アプリもできない許可に結び付いたデータなら何でも
Files.Read.All の例その人が読めるファイルだけ読めるテナントのどのファイルでも読める
誰が同意できるか自分のデータならユーザー、全員分なら管理者管理者だけ
別の呼び方スコープアプリロール

同じ Files.Read.All でも、委任とアプリケーションでは届く範囲がまったく違います。アプリケーションのアクセス許可は強いので、管理者しか同意できないようになっています。

同意(コンセント)

図3の (A)(B) で持ち主が「許可する」場面が、同意(コンセント)です。Microsoft の場合、アプリに初めてサインインしたときなどに、アプリが求めるアクセス許可の一覧と、アプリの発行元の情報が表示され、ユーザーは許可するかどうかを選びます。強い権限を求めるアプリには、管理者の同意が必要になります。

アプリサインインのとき同意を求める内容・求めるアクセス許可の一覧・アプリの発行元の情報ここを見て、許可するかを決めるユーザーの同意自分のデータについて、自分で許可する管理者の同意アプリケーションの許可や、強い委任の許可。組織全員の分をまとめて許可できる同意すると、アプリはその範囲でデータにアクセスできるようになる
図13 同意(コンセント)のしくみ(Microsoft の場合)

OAuth 2.0 は「認可」のしくみ

OAuth 2.0 は「アプリに何をさせてよいか」を決める認可のしくみで、「誰がサインインしたか」を確かめる認証のしくみではありません。Microsoft の API Management のドキュメントも、OAuth 2.0 は認証のプロトコルではなく、ユーザーの認証やシングルサインオンを足す OpenID Connect と一緒に使われることが多いと説明しています。

OpenID Connect は OAuth 2.0 を広げて、ID トークンという「誰がサインインしたか」を表すトークンを足します。

では、OAuth 2.0 と OpenID Connect は、実際の流れのどこで分かれるのでしょうか。人がサインインして API も呼ぶ、よくある場面を順に並べると次のようになります。白抜きの札が OAuth 2.0 の部分、塗りつぶしの札が OpenID Connect が足す部分です。

OAuth 2.0 の部分OpenID Connect が足す部分認可サーバ(Entra ID など)人ブラウザアプリクライアント認可エンドポイントトークンエンドポイントAPIリソースサーバ① サインインへ案内scope に openid② サインインして同意API の範囲アプリに届く③ 認可コードを付けて戻す④ 認可コード + アプリの証明⑤ トークンが2つ届くアクセストークンID トークン⑥ ID トークンを確かめて「誰がサインインしたか」を知る⑦ アクセストークンを付けて API を呼ぶ⑧ アクセストークンを確かめ、許可された範囲で応じる流れは OAuth 2.0 の認可コードのまま。openid の範囲と ID トークンが足される
図14 OAuth 2.0 と OpenID Connect の分担(人がサインインして API も呼ぶ場合)
  • 流れそのものは OAuth 2.0:①〜⑤は、図5で見た認可コードの流れと同じです。Microsoft のドキュメントも、OpenID Connect のサインインの要求は、OAuth 2.0 の認可コードの最初の手順とよく似ていると説明しています。
  • OpenID Connect が足すもの:①で範囲に openid を入れると、⑤でアクセストークンと一緒に ID トークンが届きます。⑥でアプリが ID トークンを確かめ、「誰がサインインしたか」を知ります。これがサインイン(認証)です。
  • API を呼ぶのは OAuth 2.0:⑦⑧で API に見せるのはアクセストークンです。ID トークンは API には渡しません。

OpenID Connect は新しい流れを作るのではなく、OAuth 2.0 の流れに「誰がサインインしたか」を運ぶ ID トークンを足したもの、と考えると分かりやすくなります。アクセストークンは API に見せるもの、ID トークンはアプリ自身が読むもの、と覚えると混ざりません。Microsoft も、ID トークンをリソースへのアクセスの認可には使わないよう書いています。

ID トークンがあると、アプリに何ができるのか

アクセストークンだけでも、アプリは API を呼べます。では、ID トークンがあると何がうれしいのでしょうか。違いは、アプリが自分で「誰がサインインしたか」を確かめられるかどうかです。

アクセストークンだけID トークンもあるアプリAPI 宛ての通行証(アプリは読まない)?誰がサインインした?× 誰がサインインしたか、確かめられない× ログインした状態を作れない× 「ようこそ ○○さん」と出せない× アプリのデータを本人に結び付けられないできるのは、API を呼ぶことだけアプリ読んで確かめるID トークン(アプリ宛て)宛先:このアプリ誰:tid + oid名前・メール(profile・email)◯ ログインした状態を作れる期限(exp)に合わせてセッションを保つ◯ 名前やメールを画面に出せる「ようこそ ○○さん」◯ データを本人に結び付けられるtid + oid をキーにする◯ ほかのアプリとも SSO一度のサインインで済むID トークンは、アプリ自身に宛てた「この人がサインインしました」という証明
図15 ID トークンがある場合とない場合

アクセストークンは API に宛てた通行証です。図8で見たとおり、アプリは中身を読みません。そのため、アクセストークンだけでは、アプリは「いま誰が使っているのか」を自分では確かめられません。

ID トークンは、アプリ自身に宛てて出されます。Microsoft のドキュメントも、ID トークンは、アプリがユーザーをサインインさせるときに使い、ユーザーの基本的な情報を得るためのものだと説明しています。アプリは ID トークンを確かめることで、次のことができるようになります。

できること使う情報たとえば
ログインした状態を作るID トークンを確かめて本人と分かったら、アプリのセッションを始める。Microsoft は、セッションの長さを ID トークンの期限(exp)に合わせるよう書いている社内ポータルに一度入れば、しばらくはそのまま使える
名前やメールアドレスを出す範囲に profile や email を入れると、名前やメールアドレスが ID トークンに入る画面の右上に「○○さん」と出す
アプリのデータを本人に結び付けるtid(テナント)と oid(ユーザー)の組み合わせは変わらない値。Microsoft は、これをデータのキーに使うよう勧めている申請の履歴や設定を、その人のものとして保存する
ほかのアプリとシングルサインオンMicrosoft のドキュメントは、OpenID Connect は ID トークンでアプリどうしのシングルサインオンを実現すると説明しているEntra ID に一度サインインすれば、別の業務アプリにも入れる

アプリ宛てだから、アプリが確かめられる

ID トークンとアクセストークンの大きな違いは、宛先です。

項目ID トークンアクセストークン
誰宛てかアプリ自身(aud がアプリのクライアント ID)API(aud が API)
中身を読むのはアプリAPI。アプリは読まない
分かること誰がサインインしたかアプリに何が許されたか
見せる相手どこにも見せない。アプリの中で使うAPI に見せる

アプリは ID トークンの署名を確かめたうえで、自分宛てか(aud が自分のクライアント ID か)、期限内か、自分が送ったサインインの要求への答えか(nonce が要求のときと同じか)を確かめます。自分宛てだと確かめられるから、「この人がサインインした」と信じてよいわけです。

  • サインインだけしたいアプリ:範囲を openid にして、ID トークンだけをもらえば済みます。
  • API も呼びたいアプリ:図14のように、ID トークンとアクセストークンの両方をもらいます。
ID トークンでしてはいけないこと

ID トークンは「誰か」を表すもので、API への通行証ではありません。Microsoft も、ID トークンをリソースへのアクセスの認可には使わないよう書いています。API にはアクセストークンを見せます。また、Microsoft のドキュメントは、ID トークンを確かめるのはサーバで動く機密クライアントであるべきだとしています。ブラウザやスマホのように利用者の手元で動くアプリでは、確かめに使う鍵を書き換えられるおそれがあり、確かめても効果がないためです。

動かす前の準備:認可サーバに登録しておくこと

ここまでの流れは、いきなり動くわけではありません。動かす前に、アプリと API をそれぞれ認可サーバに登録しておく必要があります。RFC 6749 も、クライアントはプロトコルを始める前に認可サーバに登録する、と書いています。登録の方法は認可サーバごとに違うので、ここからは Microsoft(Entra ID)の場合で見ます。

認可サーバ(Entra ID など)ここに登録して、信頼の中心にする① アプリの登録クライアント ID・リダイレクト URI資格情報(シークレット/証明書の公開鍵)使いたい API の許可② API の登録アプリ ID URI(API の名前)範囲(スコープ)・アプリロール③ 同意(ユーザー/管理者)アプリ持つもの:クライアント IDシークレット or証明書の秘密鍵登録するAPI持つもの:自分の宛先信頼する発行元登録する④ 公開鍵をメタデータから自動で取る(鍵は入れ替わるので、手で固定しない)アプリと API の間で、鍵や証明書を直接交換する必要はない
図16 動かす前の準備(Microsoft の場合)
準備何を設定するかねらい
① アプリの登録アプリを登録すると、クライアント ID が割り当てられる。リダイレクト URI(認可コードを返す先)と、アプリ自身の資格情報(クライアントシークレット、または証明書の公開鍵)を登録する認可サーバが「どのアプリか」「本物のアプリか」「コードをどこへ返すか」を判断できるようにする。クライアント ID は秘密ではない
② API の登録API も登録し、Expose an API でアプリ ID URI(例:api://<クライアント ID>)と範囲(スコープ)を決める。人がいない呼び出しを受けるなら、App roles でアプリロールも決めるトークンの宛先と、アプリに許せる操作の一覧を作る
③ 許可と同意アプリの登録に、使いたい API の許可を付ける。ユーザーや管理者が同意する(アプリケーションの許可は管理者だけ)どのアプリに、どの範囲を許すかを決める
④ 署名の確かめ方API は、認可サーバが公開しているメタデータから、トークンの署名を確かめる公開鍵を取るトークンが本物で、書き換えられていないことを確かめる

証明書や鍵の受け渡しは要る?

「事前に証明書を交換しておく」ような作業があるかどうかは、どの鍵の話かで分かれます。

  • アプリが証明書で自分を証明する場合:アプリの登録に、証明書の公開鍵をアップロードしておきます。秘密鍵はアプリが持ちます。Microsoft は、クライアントシークレットより証明書を使うことを勧めています。これはアプリと認可サーバの間の、一度きりの準備です。
  • トークンの署名を確かめる鍵:認可サーバはトークンを秘密鍵で署名し、対応する公開鍵を公開しています。API は、OpenID Connect のメタデータ(下の URL)にある jwks_uri から公開鍵を取ります。手で受け渡しするものではありません。
  • アプリと API の間:鍵や証明書を直接交換する必要はありません。どちらも認可サーバとだけつながり、認可サーバを信頼の中心にします。

Entra ID の OpenID Connect のメタデータの場所(Microsoft のドキュメントより)

https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
鍵は入れ替わる

Microsoft の署名の鍵は定期的に入れ替わり、緊急時にはすぐに入れ替わることもあります。入れ替わりの間隔は決まっていません。Microsoft は、公開鍵を手で設定して固定するのではなく、標準のライブラリでメタデータから自動で取り直すよう勧めています。公開鍵を手で更新するアプリは、安全性も信頼性も下がるとしています。

注意点

注意点理由と対策
トークンを漏らさないBearer トークンは持っている人が使える。必ず HTTPS で運び、ログや画面に出さない。リフレッシュトークンも機密として扱う
範囲は必要な分だけ範囲が広いほど、漏れたときの影響が大きい。特にアプリケーションのアクセス許可は、テナント全体に届くことがある
古いグラントを使わないパスワードを受け取るグラントとインプリシットは、RFC 9700 で使わない扱いになっている
リダイレクト先を正確に登録するRFC 9700 は、認可サーバがリダイレクト先の URI を文字列の完全一致で確かめることを求めている。アプリ登録のリダイレクト URI は、使うものだけを正確に登録する
同意画面をよく見る同意すると、アプリはその範囲でデータにアクセスできるようになる。求められているアクセス許可と、アプリの発行元を確かめてから許可する

企業での例

場面OAuth 2.0 での形押さえどころ
社内の Web アプリが、使っている人のメールや予定を Microsoft Graph で読む委任されたアクセス許可。認可コードでトークンを取るアプリは、その人が見られるものしか見られない
夜間のバックアップや自動化の仕組みが、Microsoft Graph を呼ぶアプリケーションのアクセス許可。クライアントクレデンシャルでトークンを取る管理者の同意が必要。範囲がテナント全体になりやすいので、必要な許可だけにする
社外の SaaS に「Microsoft でサインイン」し、SaaS が予定表を読むSaaS がクライアント、Entra ID が認可サーバ、Microsoft Graph がリソースサーバ同意画面で、求められているアクセス許可と発行元を確かめる
自社の API を、Entra ID のトークンで守る自社の API がリソースサーバAPI でトークンを確かめる。API Management のドキュメントも、発行元(issuer)と宛先(audience)の確認を挙げている

まとめ

項目内容
何をするしくみかパスワードを渡さずに、範囲と期限のある通行証(アクセストークン)をアプリに渡す
4つの役割リソースオーナー(持ち主)、クライアント(アプリ)、認可サーバ(通行証を出す係)、リソースサーバ(データを持つ API)
流れ許可をもらう → 許可の証をトークンに引き換える → トークンを付けて API を呼ぶ
アクセストークン範囲・期限・宛先がある。Bearer なので漏らさない(HTTPS で運ぶ)
グラント人がいるなら認可コード(PKCE と組み合わせる)、人がいないならクライアントクレデンシャル。インプリシットとパスワードのグラントは使わない
動かす前の準備アプリと API を認可サーバに登録する。API は認可サーバの公開鍵をメタデータから自動で取る。アプリと API の間で鍵を交換する必要はない
認可と認証OAuth 2.0 は認可。サインインは OpenID Connect が ID トークンで足す。ID トークンはアプリ宛てなので、アプリが「誰がサインインしたか」を確かめ、ログインした状態や本人のデータに使える

参考にした資料

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

あとがき

OAuth 2.0 は、最初に読んだとき「認可コード」「グラント」「スコープ」と用語が多くて身構えてしまいました。ただ、RFC の最初にある写真の印刷の例に戻ると、やりたいことは「パスワードを渡さずに、写真を読むことだけ任せたい」という素朴な話でした。同じ Files.Read.All でも、委任とアプリケーションで届く範囲がまったく違うのは、Entra ID でアプリ登録をするときにいちばん気をつけたい点だと感じています。

コメント