Skip to content

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

  • introspection は「トークンを解析する」ではなく「認可サーバに状態を問い合わせる」ものである。
  • 認可サーバーに対して POST リクエストを実施するだけなので、仕様自体はシンプルである。
  • リソースサーバへの全てのリクエストに、Token Introspection を実施すると認可サーバーへの負荷と依存が高まる。そのため JWT 自己検証とのトレードオフ(即時失効 / 集中管理 vs 追加通信 / 可用性)を理解して使い分ける。
  • Docker が使用できる。
  • Docker Compose が使用できる。
  • mise がインストールされている。
バージョン
MacSequoia 15.7
Keycloak26.5.0
Docker29.1.3
Docker Composev5.0.1
Terraform1.14.3
compose.yaml
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: {}
Terminal window
docker compose up -d

Keycloak の master レルムに Terraform を実行するためのクライアントを作成します。

http://localhost:8080/ にアクセスします。ユーザー名、パスワードどちらも admin です。

画像のような設定でボタンを押していきます。 keycloak-master-realm-clients keycloak-master-realm-clients-general-settings keycloak-master-realm-clients-capability-config

Login settings はなにも入力せず Save ボタンを押します。 keycloak-master-realm-clients-login-settings

先ほど作成した terraform クライアントからクライアントシークレットをコピーします。 keycloak-master-realm-client-details-terraform-credentials

Terraform 実行時のクライアントは admin ロールを付与します。 keycloak-master-realm-client-details-terraform-service-account-roles-assign-role

admin を選択し、Assign ボタンを押します。 keycloak-master-realm-client-details-terraform-service-account-roles-admin

Terraform で Keycloak(レルム / クライアント / ユーザー)を作成

Section titled “Terraform で Keycloak(レルム / クライアント / ユーザー)を作成”
terraform/terraform.tfvars
client_id = "terraform"
client_secret = "client secretをコピペ"
url = "http://localhost:8080"
Terminal window
cd terraform
terraform init
terraform plan
terraform apply -auto-approve

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 では、次の用語が定義されています

用語意味
Token Introspection本文書で定義されたネットワーク・プロトコルを使用して、OAuth 2.0 トークンの現在の状態について問い合わせる行為
Introspection Endpointトークン・イントロスペクションの操作が実行される OAuth 2.0 のエンドポイント

参考:https://datatracker.ietf.org/doc/html/rfc7662#section-1.2

保護リソースは 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 は必須ではありませんが、認可サーバ側の探索コストを下げるヒントとして扱えます

レスポンスは JSON で、最重要なのは active です。 active: false の場合、保護リソースはそのトークンを無効として扱います

参考:https://datatracker.ietf.org/doc/html/rfc7662#section-2.2

Keycloak で introspection を実行する

Section titled “Keycloak で introspection を実行する”

OIDC Discovery(.well-known/openid-configuration)から Introspection Endpoint を特定します。

参考: https://openid.net/specs/openid-connect-discovery-1_0.html

Terminal window
curl -s http://localhost:8080/realms/myrealm/.well-known/openid-configuration | jq -rM .introspection_endpoint
http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect

手元で Authorization Code + PKCE を再現し、アクセストークンを取得します。

Terminal window
CLIENT_ID=myapp
CLIENT_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 openid

Keycloak では、アクセストークンを Requesting Party Token(RPT)として扱うケースがあります。 token_type_hint=requesting_party_token を付与し、active の変化を確認します。

Terminal window
CLIENT_ID=myapp
CLIENT_SECRET=2KYxImIXlm1GcZakwEpgYLlefrOzgg33
ACCESS_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

有効時は active: true が返ります。

{
"exp": 1767796942,
"iat": 1767796642,
"jti": "onrtac:48a6d797-2c5e-4375-8666-11fb7a3ef378",
"typ": "Bearer",
"acr": "0",
"active": true
}

期限切れ時は active: false のみが返ります。

{
"active": false
}

参考:https://www.keycloak.org/docs/26.5.0/authorization_services/index.html#_service_protection_token_introspection

Token Introspection Endpoint を使用すると現在のトークンの状態を確認できます。
しかし、リクエストごとに問い合わせる設計にすると、認可サーバの負荷と依存度が上がります。
そのため全てのリクエストで introspection を実施するのではなく、実施場所を取捨選択する必要があります。

以下のような要求・要件があった場合には Token Introspection Endpoint の利用を検討するのが良いかもしれません。

  • トークンが Opaque token で、リソースサーバ単体で自己検証できない場合
  • トークンの取り消し(revocation)を比較的短い時間で反映したい場合
  • 認可判断に必要なメタデータ(権限、認可コンテキストなど)を認可サーバを正として取得したい場合
  • 認可判断を入口に集約し、API Gateway / BFF で検証結果を内側へ伝搬したい場合

Token Introspection Endpoint を使用する、と仮定した場合、リクエストは以下のタイミングを検討するのが良いと考えています。

  • Opaque token を受け取った初回に introspection を実行する。
  • API Gateway / BFF で 1 回だけ introspection し、内部サービスへは検証結果(subject、scopes など)を伝搬する。
  • 高リスク操作(送金、権限変更など)の直前に追加で introspection を実行する。
  • JWT の通常リクエストは自己検証で処理し、毎回の introspection は避ける。
  • introspection レスポンスは、性能向上と負荷軽減のためにキャッシュする。
  • キャッシュ TTL は「許容できる失効反映遅延」と「exp」を上限に決める。
  • 応答に exp がある場合、exp を超えてキャッシュしない。
  • 高機密の保護リソースでは、古い情報のリスクを避けるためにキャッシュを無効化する選択肢を持つ。

JWT は署名検証と exp 検証で認可判断できるため、通常は introspection の頻度を下げられます。

  • 通常の API リクエストは自己検証で処理する。
  • 即時失効を重視する場合、短い TTL のキャッシュを併用して introspection を使う。
  • 例外時(疑わしいトークン、鍵ローテ直後の整合性確認など)に限って introspection を使う。

Opaque token はリソースサーバ単体での自己検証が難しいため、introspection を前提にした設計になりやすいです。

  • 原則として introspection を実行する。
  • 毎リクエストの問い合わせを避け、短い TTL でキャッシュする。