OAuth 2.0 Token Introspection を Keycloak で検証する
- この記事でわかること(結論・再現手順・裏取り・注意点)を把握できる。
- introspection を「トークン解析」ではなく「認可サーバへの状態問い合わせ」として理解できる。
OAuth 2.0 の学習を進めていると Token Introspection について知ったので、RFC を読みながら実際に Keycloak を動かして確認してみます。
- Keycloak で introspection を再現し、RFC 7662 で裏取りできるようにする。
- JWT 自己検証で済むケースと、Opaque token で問い合わせが要るケースを切り分けできるようにする。
https://github.com/kntks/blog-code/tree/main/2026/01/keycloak-token-introspection
結論 / 学んだこと
Section titled “結論 / 学んだこと”- introspection は「トークンを解析する」ではなく「認可サーバに状態を問い合わせる」ものである。
- 認可サーバーに対して POST リクエストを実施するだけなので、仕様自体はシンプルである。
- リソースサーバへの全てのリクエストに、Token Introspection を実施すると認可サーバーへの負荷と依存が高まる。そのため JWT 自己検証とのトレードオフ(即時失効 / 集中管理 vs 追加通信 / 可用性)を理解して使い分ける。
- Docker が使用できる。
- Docker Compose が使用できる。
- mise がインストールされている。
| バージョン | |
|---|---|
| Mac | Sequoia 15.7 |
| Keycloak | 26.5.0 |
| Docker | 29.1.3 |
| Docker Compose | v5.0.1 |
| Terraform | 1.14.3 |
Keycloak の起動
Section titled “Keycloak の起動”services: keycloak: image: quay.io/keycloak/keycloak:26.5.0 ports: - target: 8080 published: 8080 protocol: tcp mode: host environment: - KEYCLOAK_ADMIN=admin - KEYCLOAK_ADMIN_PASSWORD=admin command: ["start-dev"] volumes: - keycloak_data:/opt/keycloak/data
volumes: keycloak_data: {}docker compose up -dTerraform 用 Client 作成
Section titled “Terraform 用 Client 作成”Keycloak の master レルムに Terraform を実行するためのクライアントを作成します。
http://localhost:8080/ にアクセスします。ユーザー名、パスワードどちらも admin です。
画像のような設定でボタンを押していきます。

Login settings はなにも入力せず Save ボタンを押します。

先ほど作成した terraform クライアントからクライアントシークレットをコピーします。

Terraform 実行時のクライアントは admin ロールを付与します。

admin を選択し、Assign ボタンを押します。

Terraform で Keycloak(レルム / クライアント / ユーザー)を作成
Section titled “Terraform で Keycloak(レルム / クライアント / ユーザー)を作成”client_id = "terraform"client_secret = "client secretをコピペ"url = "http://localhost:8080"cd terraformterraform initterraform planterraform apply -auto-approveRFC 7662
Section titled “RFC 7662”Bearer Token の前提(RFC 6750)
Section titled “Bearer Token の前提(RFC 6750)”仕様が解決する課題
Section titled “仕様が解決する課題”OAuth 2.0 ではトークンの内容はクライアントに対して opaque です。その一方で、トークンには現在の有効性、承認済みスコープ、発行時のコンテキストなどのメタデータが紐づく場合があります。 これらは保護リソースがトークンに基づいて認可判断する際に重要です
OAuth 2.0 は、リソースサーバが認可サーバから受け取ったトークンのメタ情報を取得するためのプロトコルを定義していません。 RFC 7662 は、このギャップを埋めるために、保護リソースが認可サーバへメタデータを問い合わせるプロトコルを定義します
この定義により、保護リソースが認可サーバに問い合わせることで次のことがわかります。
- トークンが現在有効かどうか(期限切れや取り消しの有無を含む)
- トークンが持つアクセス権(通常は OAuth 2.0 のスコープで表現される)、およびトークンが付与された認可の文脈(誰がトークンを認可したか、どのクライアントに発行されたかなど)
参考: https://datatracker.ietf.org/doc/html/rfc7662#section-1
This specification defines a protocol that allows authorized protected resources to query the authorization server to determine the set of metadata for a given token that was presented to them by an OAuth 2.0 client.
訳:この仕様は、OAuth 2.0 クライアントが提示したトークンについて、認可された保護リソースが認可サーバーに問い合わせてそのトークンに関するメタデータの集合を判定できるプロトコルを定義する。
引用:https://datatracker.ietf.org/doc/html/rfc7662#section-1
用語(RFC 7662 1.2)
Section titled “用語(RFC 7662 1.2)”RFC 7662 では、次の用語が定義されています
| 用語 | 意味 |
|---|---|
| Token Introspection | 本文書で定義されたネットワーク・プロトコルを使用して、OAuth 2.0 トークンの現在の状態について問い合わせる行為 |
| Introspection Endpoint | トークン・イントロスペクションの操作が実行される OAuth 2.0 のエンドポイント |
参考:https://datatracker.ietf.org/doc/html/rfc7662#section-1.2
リクエスト(RFC 7662 2.1)
Section titled “リクエスト(RFC 7662 2.1)”保護リソースは Introspection Endpoint に対して、HTTP POST で問い合わせます。
Content-Type は application/x-www-form-urlencoded で、最低限 token パラメータを送信します
The protected resource calls the introspection endpoint using an HTTP POST [RFC7231] request with parameters sent as “application/x-www-form-urlencoded” data as defined in [W3C.REC-html5-20141028].
訳:保護されたリソースは、パラメータを [W3C.REC-html5-20141028] で定義された “application/x-www-form-urlencoded” データとして送信し、HTTP POST [RFC7231] リクエストを用いてイントロスペクション・エンドポイントを呼び出します。
引用:https://datatracker.ietf.org/doc/html/rfc7662#section-2.1
token_type_hint は必須ではありませんが、認可サーバ側の探索コストを下げるヒントとして扱えます
レスポンス(RFC 7662 2.2)
Section titled “レスポンス(RFC 7662 2.2)”レスポンスは JSON で、最重要なのは active です。
active: false の場合、保護リソースはそのトークンを無効として扱います
参考:https://datatracker.ietf.org/doc/html/rfc7662#section-2.2
Keycloak で introspection を実行する
Section titled “Keycloak で introspection を実行する”Introspection endpoint の特定
Section titled “Introspection endpoint の特定”OIDC Discovery(.well-known/openid-configuration)から Introspection Endpoint を特定します。
参考: https://openid.net/specs/openid-connect-discovery-1_0.html
curl -s http://localhost:8080/realms/myrealm/.well-known/openid-configuration | jq -rM .introspection_endpointhttp://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspectアクセストークンの取得
Section titled “アクセストークンの取得”手元で Authorization Code + PKCE を再現し、アクセストークンを取得します。
CLIENT_ID=myappCLIENT_SECRET=2KYxImIXlm1GcZakwEpgYLlefrOzgg33
oauth2c http://localhost:8080/realms/myrealm \ --client-id $CLIENT_ID \ --client-secret $CLIENT_SECRET \ --response-types code \ --response-mode query \ --grant-type authorization_code \ --auth-method client_secret_basic \ --pkce \ --scopes openidPOST /token/introspect の実行
Section titled “POST /token/introspect の実行”Keycloak では、アクセストークンを Requesting Party Token(RPT)として扱うケースがあります。
token_type_hint=requesting_party_token を付与し、active の変化を確認します。
CLIENT_ID=myappCLIENT_SECRET=2KYxImIXlm1GcZakwEpgYLlefrOzgg33ACCESS_TOKEN=eyJ...guw
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "token_type_hint=requesting_party_token&token=$ACCESS_TOKEN" "http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect" | jqレスポンス例
Section titled “レスポンス例”有効時は active: true が返ります。
{ "exp": 1767796942, "iat": 1767796642, "jti": "onrtac:48a6d797-2c5e-4375-8666-11fb7a3ef378", "typ": "Bearer", "acr": "0", "active": true}期限切れ時は active: false のみが返ります。
{ "active": false}Token Introspection の検討
Section titled “Token Introspection の検討”Token Introspection Endpoint を使用すると現在のトークンの状態を確認できます。
しかし、リクエストごとに問い合わせる設計にすると、認可サーバの負荷と依存度が上がります。
そのため全てのリクエストで introspection を実施するのではなく、実施場所を取捨選択する必要があります。
検討した方が良い要求・要件
Section titled “検討した方が良い要求・要件”以下のような要求・要件があった場合には Token Introspection Endpoint の利用を検討するのが良いかもしれません。
- トークンが Opaque token で、リソースサーバ単体で自己検証できない場合
- トークンの取り消し(revocation)を比較的短い時間で反映したい場合
- 認可判断に必要なメタデータ(権限、認可コンテキストなど)を認可サーバを正として取得したい場合
- 認可判断を入口に集約し、API Gateway / BFF で検証結果を内側へ伝搬したい場合
いつリクエストするか
Section titled “いつリクエストするか”Token Introspection Endpoint を使用する、と仮定した場合、リクエストは以下のタイミングを検討するのが良いと考えています。
- Opaque token を受け取った初回に introspection を実行する。
- API Gateway / BFF で 1 回だけ introspection し、内部サービスへは検証結果(subject、scopes など)を伝搬する。
- 高リスク操作(送金、権限変更など)の直前に追加で introspection を実行する。
- JWT の通常リクエストは自己検証で処理し、毎回の introspection は避ける。
キャッシュするか
Section titled “キャッシュするか”- introspection レスポンスは、性能向上と負荷軽減のためにキャッシュする。
- キャッシュ TTL は「許容できる失効反映遅延」と「exp」を上限に決める。
- 応答に
expがある場合、expを超えてキャッシュしない。 - 高機密の保護リソースでは、古い情報のリスクを避けるためにキャッシュを無効化する選択肢を持つ。
JWT の場合
Section titled “JWT の場合”JWT は署名検証と exp 検証で認可判断できるため、通常は introspection の頻度を下げられます。
- 通常の API リクエストは自己検証で処理する。
- 即時失効を重視する場合、短い TTL のキャッシュを併用して introspection を使う。
- 例外時(疑わしいトークン、鍵ローテ直後の整合性確認など)に限って introspection を使う。
Opaque token の場合
Section titled “Opaque token の場合”Opaque token はリソースサーバ単体での自己検証が難しいため、introspection を前提にした設計になりやすいです。
- 原則として introspection を実行する。
- 毎リクエストの問い合わせを避け、短い TTL でキャッシュする。