1. 主要ページへ移動
  2. メニューへ移動
  3. ページ下へ移動

QES ブログ

記事公開日

JWTを解剖してアクセストークン・IDトークンを実物で理解してみる ― 手を動かして学ぶOAuth2.0/OIDC

  • このエントリーをはてなブックマークに追加

この記事のポイント

OAuth2.0/OpenID Connectを「用語」ではなく「実物のトークン」で理解する記事です。自分のEntraアプリで認可コード+PKCEフローを踏み、発行されたJWTをjwt.msで解剖します。

  • OAuthは認可、OIDCは認証:
    アクセストークンは「リソースへのアクセスを許可する」認可のトークン、IDトークンは「本人が認証された」証明のトークン。役割が違います。
  • アクセストークンはクライアントが検証してはいけない:
    Microsoft Graphなど他リソース宛のアクセストークンは、クライアントにとって「不透明な文字列」。中身に依存せず、検証するのは受け取るリソース側の責務です(公式明記)。
  • IDトークンは自分で検証する:
    一方、自分が受け取るIDトークンは、署名・iss・audなどを検証する必要があります。この非対称性が、トークンを扱ううえでの分かれ目です。

こんにちは!DXソリューション営業本部の大和矢です。

OAuth2.0とOpenID Connect(OIDC)——情報処理安全確保支援士の試験でも頻出のテーマですね。
「アクセストークン」「IDトークン」「認可コードフロー」「PKCE」…用語は一通り追えるのに、いざ説明しようとするといまひとつ腹落ちしていない、という経験はありませんか?

筆者もそうです。ということで、今回は筆者のお勉強にお付き合いください。
具体的には、自分のEntraアプリで認可コード+PKCEフローを実際に踏み、発行されたJWT(JSON Web Token)を解剖して仕様と突き合わせる、という手を動かすアプローチを取ります。
トークンの"実物"を一度見ておくと、解像度が変わります。

この記事のゴール:
「アクセストークンとIDトークンは何が違うのか」「なぜアクセストークンはクライアントが検証してはいけないのか」——この2つを、公式ドキュメントの記述と実物のトークンの両方で腹落ちさせることです。
検証は、ブラウザ・Windows PowerShell・jwt.ms だけで完結します(いずれもWindows標準またはWeb上のツールで、追加インストール・費用は不要)。

OAuth2.0とOIDCの役割分担

まず押さえるべきは、OAuthは「認可」、OIDCは「認証」という役割分担です。
Microsoftの公式ドキュメントも「このプラットフォームでは、OAuthが承認(認可)に使用され、OpenID Connect(OIDC)が認証に使用されます」と明記しています(認証と認可)。

認証(Authentication)は「あなたは誰か(本人か)」を確かめること、認可(Authorization)は「何をしてよいか(権限)」を与えることです。
OIDCはOAuth2.0の上に構築されているため、用語やフローがよく似ていて混同しがちですが、この2つは目的が違います。

この違いが、そのまま2種類のトークンに対応します。

項目 IDトークン アクセストークン
プロトコル OpenID Connect(認証) OAuth2.0(認可)
目的 ユーザーが認証された証明 リソースへのアクセス許可
受け取る側 クライアント(自分) リソースサーバー(API側)
API呼び出しに使う 使わない(公式が明記) 使う

公式ドキュメントは「API を呼び出すために ID トークンを使用しないでください」とはっきり書いています(ID トークン)。
IDトークンは"本人確認の証明書"、アクセストークンは"入館証"、と役割が分かれている点をまず押さえてください。

認可コード+PKCEフローを実際に踏む

では実際にトークンを取得してみます。
使うのは、現在もっとも標準的な認可コードフロー+PKCE(Proof Key for Code Exchange)です。

PKCEは、認可コードの"横取り"を防ぐための拡張です。
クライアントはまず、ランダムな秘密(code_verifier)を生成し、そのハッシュ(code_challenge)だけを認可リクエストに載せて送ります。
そして認可コードをトークンに交換する段階で、元の code_verifier を一緒に送り、認可サーバーが「先に受け取った code_challenge と対応するか」を検証します。
この仕組みにより、たとえ通信経路や別アプリ経由で認可コードを盗まれても、code_verifier を知らない攻撃者はトークンに交換できません(この code_verifier / code_challenge は、後述のリクエスト例にそのまま登場します)。

PKCEは、Microsoft IDプラットフォームではSPA(Single-Page Application=ページ遷移せず1枚の画面をJavaScriptで書き換えるブラウザ内アプリ。React/Angular/Vueなど)で必須、ネイティブアプリや機密クライアント(Webサーバー側で秘密を安全に保持できるアプリ)で推奨です(2026年7月時点、サード パーティ Cookie のブロック)。

全体像は次のとおりです。クライアント(自分のアプリ)Microsoft Entra IDMicrosoft Graphの3者が、①〜⑥の順にトークンをやり取りします。
以降の手順は、この図の①〜⑥を実際に踏んでいく流れです。

認可コード+PKCEフローのシーケンス図。①クライアントがMicrosoft Entra IDへ認可要求(code_challenge付き)、②認可コードを受領、③code+code_verifierでトークン交換、④id_token・access_token・refresh_tokenを取得、⑤access_tokenでMicrosoft Graphを呼び出し、⑥ユーザー情報を取得。id_tokenはクライアント自身が検証し、access_tokenはGraphが検証するという非対称性も示す

準備として、Azure(Microsoft Entra管理センター)でアプリを登録します。

Microsoft Entra管理センターの「認証」設定画面。プラットフォームがモバイルとデスクトップアプリケーション、リダイレクトURIがhttp://localhostに設定されている

フローの流れはシンプルです。
まず認可エンドポイントに code_challenge を付けてアクセスし、サインイン・同意の後に短命の認可コードを受け取ります。
リダイレクトURIには http://localhost を使います(システムブラウザを使うクライアントでの推奨値)。
認可後、認可コードは http://localhost/?code=... の形でブラウザのアドレスバーに返るので、そこから code を取り出します(ページ自体は「アクセスできません」と表示されますが、URLの code が拾えれば問題ありません)。

GET https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
  ?client_id={clientId}
  &response_type=code
  &redirect_uri=http://localhost
  &scope=openid profile offline_access User.Read
  &code_challenge={code_challenge}
  &code_challenge_method=S256
  &state={ランダム値}
  • client_id
    ステップ1で登録したアプリの識別子。
  • response_type=code
    認可コードフローを使うことを示す固定値。
  • redirect_uri
    認可後にコードを受け取るURL。登録したリダイレクトURIと完全に一致させる必要がある。
  • scope
    要求する権限。openid=IDトークンの発行、profile=氏名等の追加クレーム、offline_access=リフレッシュトークン、User.Read=Microsoft Graphでの自分のプロフィール読み取り。
  • code_challenge / code_challenge_method
    PKCEのハッシュ値と、その生成方式(S256)。上で説明したとおり、code_verifier をハッシュ化して作る値。
  • state
    CSRF対策のランダム値。リダイレクト後、送信時と同じ値が返ってきたかを確認する。

次に、受け取った認可コードを、code_verifiercode_challengeの元になった秘密)と一緒にトークンエンドポイントへ送り、トークンに交換します。
このステップは POST なので、ブラウザだけでは踏めません。
今回は追加アプリを入れず、Windows標準の PowerShellInvoke-RestMethod)から実行します。

POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id={clientId}
&code={認可コード}
&code_verifier={code_verifier}
&redirect_uri=http://localhost
  • grant_type=authorization_code
    認可コードをトークンに交換する要求であることを示す固定値。
  • client_id
    認可リクエストと同じアプリの識別子。
  • code
    認可エンドポイントから受け取った認可コード。
  • code_verifier
    code_challengeの元になった秘密の値。ここで初めて明かすことで、認可コードを盗んだだけの第三者にはトークンへ交換できないようにする。
  • redirect_uri
    認可リクエストで使ったものと完全に一致させる必要がある。

成功すると、access_token(アクセストークン)、id_tokenopenidスコープ要求時)、refresh_tokenoffline_access要求時)が返ってきます。

なお、こうして生のHTTP要求を手で組み立てるのは仕組みを理解するための学習用です。
実運用のアプリでは、公式も「生の HTTP 要求を手動で作成して発行することはお勧めしない。代わりにMSAL(Microsoft Authentication Library)などのサポートされたライブラリを使う」と案内しています(認可コードフロー)。

トークンエンドポイントのレスポンスJSON。access_token・id_token・refresh_tokenが返っている(トークン値はマスク)

実際に認可コード+PKCEフローを踏んだところ、access_tokenid_tokenrefresh_tokenの3つがすべて返ってきました。
仕様どおりに動くことを、自分の手元で確認できたことになります。

JWTを解剖する(jwt.msでデコード)

取得したIDトークンを解剖してみましょう。
Microsoftは、検証・デバッグ用のデコードサイトとして jwt.ms を案内しています。
ただし公式は、これを「検証とデバッグのみを目的として」使うものと釘を刺しており、本番のアプリロジックに組み込む用途ではありません(アクセストークン)。

JWTはヘッダー・ペイロード(クレーム)・署名の3要素が、ピリオド(.)で区切られ、それぞれBase64でエンコードされた構造です。
jwt.msに貼ると、この3要素が読める形で展開されます。

jwt.msでIDトークンをデコードした画面。ヘッダーのtyp・alg・kidと、クレームのiss・aud・sub・exp・iat・tid・oid・preferred_usernameなどが一覧表示されている

主なクレーム(トークンに含まれる情報)を、支援士の設問で問われる観点と対応させると次のようになります。

クレーム 意味 着目点
iss 発行者(トークンを発行したSTSのURI) 検証時は完全一致を確認
aud 対象者(このトークンは誰宛か) 宛先の取り違えは危険
sub サブジェクト(主体の不変ID) 再利用されない識別子
exp / nbf / iat 失効 / 有効化 / 発行の各時刻 有効期限・リプレイ対策
scp 委任スコープ(アクセストークンのみ) 許可された操作の範囲
tid / oid テナントID / オブジェクトID 不変。認可判断に使えるID
preferred_username / name 表示用のユーザー名・名前 変更可能=認可判断に使わない

なぜ name で権限を判断してはいけないのか:
preferred_usernamename は表示用の値で、変更される可能性があります。
公式ドキュメントも、preferred_username について「変更可能であるため、この値は承認の決定には使用できません」、name について「表示目的でのみ使用する必要があります」と明記しています(IDトークンの要求のリファレンス)。
ユーザーの識別・認可には、人間が読める値ではなく、不変な oid(オブジェクトID)や sub(サブジェクト)を使うのが原則です。

落とし穴:検証していいトークン、してはいけないトークン

ここが本記事で一番伝えたいポイントです。
アクセストークンは、受け取ったクライアントが検証してはいけません。
「トークンなんだから中身を検証するのが安全では?」と思いがちですが、これは誤解です。

Microsoftの公式ドキュメント(アクセストークン)は、次のように明記しています。

(日本語・公式)
「クライアント アプリケーションはアクセス トークンを受け取って使用できますが、それらを不透明な文字列として扱う必要があります。クライアント アプリケーションは、アクセス トークンの検証を試みることはできません。リソース サーバーは、アクセス トークンを認可の証明として受け入れる前に検証する必要があります。」

(英語・原文)
"Although client applications can receive and use access tokens, they should treat them as opaque strings. The client application shouldn't attempt to validate access tokens. The resource server should validate the access token before accepting it as proof of authorization."

つまり、Microsoft Graphなど他のリソース宛に発行されたアクセストークンは、あなたのアプリにとっては中身を覗く対象ではなく、そのまま渡す"切符"です。
検証するのは、それを受け取るリソースサーバー(API側)の責務です。

実際にやってみると分かること:
今回、Microsoft Graph向けに発行されたアクセストークンを試しにjwt.msに貼ってみたところ、素直にデコードできました。ヘッダーにtypnoncealgkid、ペイロードにaudscpなどが表示されます。
注目すべきは宛先 aud00000003-0000-0000-c000-000000000000 になっている点です。これはMicrosoft GraphのリソースIDで、このトークンが「自分のアプリ宛」ではなく「Graph宛」に発行されたことを示しています。
発行者 iss も、先ほどのIDトークンが login.microsoftonline.com/<テナント>/v2.0(v2.0形式)だったのに対し、このアクセストークンは sts.windows.net/<テナント>/(v1.0形式)で、ver1.0。同じフローで同時に受け取ったトークンなのに、宛先も形式も別物だと読み取れます。
もっとも、今回は素直に読めましたが、いつもそうとは限りません
公式ドキュメントも、Microsoftサービス向けのトークンについて「JWTとして検証されない特殊な形式が使われる場合がある」と注意しています。
ただし、これは"場合がある"であって"必ずそうなる"わけではありません。読めるかどうかはトークン次第、というのが実情です。
ここでの本当の落とし穴は「デコードできるかどうか」ではなく、デコードできてしまうからといって、その中身(aud・scp等)に依存した実装をしてよいわけではないという点です。検証・依存してよいのは、あくまでそのトークンの宛先であるリソースサーバー(Graph API)側だけです。

jwt.msでMicrosoft Graph向けアクセストークンをデコードした画面(前半)。ヘッダーのtyp・nonce・alg・kidと、aud(Microsoft GraphのリソースID 00000003-0000-0000-c000-000000000000)・iss(sts.windows.net)が表示されている
jwt.msでMicrosoft Graph向けアクセストークンをデコードした画面(後半)。scp(openid profile User.Read email)やver(1.0)などのクレームが表示されている

一方で、自分が受け取るIDトークンは検証するのが正解です。
IDトークンは「あなた(クライアント)宛」に発行された本人確認の証明なので、署名・発行者(iss)・対象者(aud)・有効期限(exp)などを検証します。
この「アクセストークンは検証しない/IDトークンは検証する」という非対称性が、トークンを扱ううえでの最重要ポイントです。

リソース側がトークンを検証するときの観点

参考までに、API(リソースサーバー)側が受け取ったトークンを検証する場合の観点も挙げておきます。

  • 署名:
    ヘッダーの alg(例 RS256)と kid(鍵の識別子)を使い、OIDCメタデータの jwks_uri から取得した公開鍵で検証する。
  • aud の一致:
    自分(API)を宛先とするトークンだけを受け入れる。宛先違いを受け入れると「confused deputy(混乱した代理人)」問題になる。
  • iss の完全一致・有効期限:
    発行者を厳密に照合し、exp/nbf/iat の時刻を確認する。
  • 鍵のローテーション:
    Microsoft Entra IDは署名鍵をいつでもロールオーバーする可能性があり、ロール間隔は保証されない。公式は、公開鍵のキャッシュ有効期限を24時間とし、更新チェックは1時間ごとに行うことを推奨している(署名キーのロールオーバー)。

よくある質問

Q. アクセストークンとIDトークンの違いは何ですか?

アクセストークンはリソースへのアクセスを許可する「認可」のトークンで、受け取るのはリソースサーバー(API側)です。
IDトークンは本人が認証されたことを示す「認証」のトークンで、受け取るのはクライアント(自分)です。公式は「API呼び出しにIDトークンを使わないこと」と明記しています。

Q. なぜアクセストークンをクライアントが検証してはいけないのですか?

アクセストークンはクライアントにとって「不透明な文字列」として扱うべきもので、中身は宛先のAPIのためのものだからです。
公式ドキュメントも「クライアントは検証を試みてはならず、検証するのはリソースサーバーの責務」と明記しています。実際にはjwt.msで素直にデコードできてしまうこともありますが、デコードできることと、中身に依存して良いことは別です。デコードできても、検証・信頼するのはリソースサーバー側の責務のままです。

Q. jwt.ms は本番でも使えますか?

いいえ。jwt.msはMicrosoftが案内する検証・デバッグ専用のデコードサイトで、本番のアプリロジックに組み込む用途ではありません。
トークンの中身を学習・確認する道具として使い、コードでトークンの中身に依存しないようにします。

まとめ:トークンは"切符"、実物で仕様を確かめる

今回は、認可コード+PKCEフローを実際に踏み、発行されたJWTをjwt.msで解剖しました。
学びは次の3つです。

  • OAuthは認可・OIDCは認証:
    アクセストークン(入館証)とIDトークン(本人確認証)は役割が違う。IDトークンでAPIを呼ばない。
  • アクセストークンはクライアントが検証しない:
    他リソース宛のアクセストークンは不透明な文字列。中身に依存せず、検証はリソース側の責務。
  • IDトークンは自分で検証する:
    署名・iss・aud・expを検証する。この非対称性が設計判断の分かれ目。

トークンは、中身を詮索する対象ではなく、役割の決まった"切符"です。
用語だけで覚えていたOAuth/OIDCも、実物のJWTを一度解剖すると、設計判断のときに迷いが減ります。
ブラウザとPowerShell、jwt.msだけで試せるので、まずは自分のIDトークンを1枚、解剖してみてはいかがでしょうか。

ここまでお読みいただき、ありがとうございました。
本記事で扱った「トークンの検証」や「アクセストークンの宛先(audience)」を、Azure API Management・MCPで実際に実装した例として、次の記事もあわせてご覧いただけますと幸いです。

QUICK E-Solutionsでは、AIを活用した業務効率化や、Microsoft Entra IDをはじめとした高度なセキュリティシステムの導入・設計をお手伝いしております。
それ以外でも様々なアプリケーションの開発・導入を行っております。提供するサービス・ソリューションにつきましては こちら に掲載しております。
システム開発・構築でお困りの問題や弊社が提供するサービス・ソリューションにご興味を抱かれましたら、ぜひ一度 お問い合わせ ください。

※このブログで参照されている、Microsoft、Azure、Microsoft Entra ID、Microsoft Graphは、米国Microsoft Corporationの米国およびその他の国における商標または登録商標です。

  • このエントリーをはてなブックマークに追加

お問い合わせ

Contact

ご質問やご相談、サービスに関する詳細など、何でもお気軽にご連絡ください。下記のお問い合わせフォームよりお気軽に送信ください。

お問い合わせ

資料ダウンロード

Download

当社のサービスに関する詳細情報を掲載した資料を、下記のページよりダウンロードいただけます。より深く理解していただける内容となっております。ぜひご活用ください。

資料ダウンロード