- Published on
API GatewayでのOIDCトークン検証 — Istio、Envoy、Gateway API実践
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — トークン検証はどこで行うべきか
- エッジでの検証 vs サービスでの検証
- Envoy jwt_authnフィルター詳説
- Istio — RequestAuthentication + AuthorizationPolicy
- JWKSキャッシングと障害モード
- Audience戦略
- トークン伝播 — 元のトークンの転送 vs Token Exchange
- Kong vs APISIX — OIDCプラグイン観点の比較
- Gateway API時代の認証標準化
- mTLSとJWTの組み合わせ — サービスの身元とユーザーの身元
- パフォーマンスの観点
- トラブルシューティング — 401デバッグのフローチャート
- おわりに
- 参考資料
はじめに — トークン検証はどこで行うべきか
マイクロサービスアーキテクチャで最も頻繁に繰り返されるセキュリティ論争の一つが「JWTの検証はどこで行うか」です。ゲートウェイで一度だけ?すべてのサービスで?両方?2026年現在、この問いに対する業界のコンセンサスは比較的明確になりました。**エッジでふるいにかけ、サービスで再検証する(defense in depth)**です。そしてその実装手段として、Envoyベースのスタック(Istio、Envoy Gateway、Glooなど)が事実上の標準になりました。
本記事ではEnvoyのjwt_authnフィルターを基礎から理解し、IstioのRequestAuthentication + AuthorizationPolicyの組み合わせを豊富なYAML例で見ていきます。JWKSキャッシングと障害モード、audience戦略、トークン伝播パターン(RFC 8693 Token Exchangeを含む)といった運用上の難題を押さえ、Kong/APISIXのOIDCプラグイン比較、Gateway API時代の認証標準化の流れ、mTLSとJWTの組み合わせまで扱います。最後に401デバッグのフローチャートをASCIIで整理します。
エッジでの検証 vs サービスでの検証
まず両アプローチのトレードオフを整理します。
| 項目 | エッジ(ゲートウェイ)のみで検証 | 各サービスのみで検証 |
|---|---|---|
| パフォーマンス | 検証1回、内部はコストゼロ | ホップごとに検証コストが繰り返される |
| 一貫性 | 中央ポリシー、設定は1か所 | サービスごとのライブラリ/設定の断片化 |
| 内部侵害への対応 | ゲートウェイを迂回されると無防備 | 内部トラフィックも検証され堅牢 |
| クレームの活用 | ヘッダーでの伝達が必要(偽造リスク管理) | サービスが直接クレームへアクセス |
| 運用負担 | 低い | ライブラリのバージョン、JWKS管理が分散 |
結論は両者の組み合わせです。実務での推奨パターンは次のとおりです。
- エッジ(ゲートウェイ): 署名、発行者(iss)、有効期限(exp)、audience(aud)を検証し、不正なトラフィックを早期に遮断します。高価な内部リソースがゴミトークンに浪費されません。
- サービス(サイドカーまたはライブラリ): 同じ検証を繰り返しつつ、サービスごとのaudienceと細粒度の認可(スコープ、ロール)を追加します。ゲートウェイが突破されたり、内部から偽造された呼び出しが来ても防御できます。
- サービス間の信頼はmTLSで: ユーザートークンとは別に、呼び出し元サービスの身元はmTLS(SPIFFEなど)で証明します。後段で再び扱います。
[エッジ: 一次検証 - 署名/iss/exp/aud]
Client ──> API Gateway (Envoy jwt_authn) ──┐
│ mTLS (サービスの身元)
v
[サービス: 二次検証 + 細粒度の認可]
Service A (sidecar RequestAuthentication)
│ トークンrelay or exchange
v
Service B (sidecar + AuthorizationPolicy)
Envoy jwt_authnフィルター詳説
IstioでもEnvoy GatewayでもKongの一部モードでも、最下層でJWTを検証しているのはEnvoyのHTTPフィルターjwt_authnです。原理を知れば、上位の抽象化の動作と障害を正確に理解できます。
中核となる概念は2つです。
- providers — 「どの発行者のトークンを、どの鍵で、どう検証するか」の定義。issuer、audiences、JWKSのソース、トークン抽出位置、ペイロードの引き渡し方法を指定します。
- rules — 「どのルートにどのproviderを要求するか」のマッピング。requiresでproviderを指定し、allow_missingやallow_missing_or_failedといった緩和モードもあります。
設定全体の例は次のとおりです。
http_filters:
- name: envoy.filters.http.jwt_authn
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
keycloak_provider:
issuer: https://keycloak.example.com/realms/prod
audiences:
- orders-api
remote_jwks:
http_uri:
uri: https://keycloak.example.com/realms/prod/protocol/openid-connect/certs
cluster: keycloak_jwks_cluster
timeout: 3s
cache_duration: 600s
async_fetch:
fast_listener: false
retry_policy:
num_retries: 3
# Authorization: Bearer ヘッダーから抽出(デフォルト)
from_headers:
- name: Authorization
value_prefix: 'Bearer '
# 検証済みペイロードをメタデータに格納し後続フィルター(RBACなど)が利用
payload_in_metadata: jwt_payload
# 検証後、アップストリームへ平文クレームヘッダーとして転送
claim_to_headers:
- header_name: x-jwt-sub
claim_name: sub
- header_name: x-jwt-scope
claim_name: scope
# 元のトークンをアップストリームへ残すか除去するか
forward: true
# exp検証のクロックスキュー許容
clock_skew_seconds: 30
rules:
# ヘルスチェックはトークン不要
- match:
prefix: /healthz
# 公開ドキュメントはトークンがあれば検証、なくても通過
- match:
prefix: /docs
requires:
requires_any:
requirements:
- provider_name: keycloak_provider
- allow_missing: {}
# それ以外はすべて必須
- match:
prefix: /
requires:
provider_name: keycloak_provider
設定項目ごとの注意点は次のとおりです。
- issuerはトークンのissクレームと文字列単位で完全一致しなければなりません。末尾スラッシュ1つの違いで401になります。
- remote_jwksのclusterは別途定義が必要です。EnvoyはJWKSエンドポイントもクラスターとして抽象化するため、DNS/TLS設定が漏れると鍵を取得できず、すべてのリクエストが401になります。
- async_fetchを有効にすると、リスナー起動時にJWKSを事前取得し、バックグラウンドで更新します。初回リクエストの遅延とJWKSエンドポイントの瞬断の影響を軽減します。
- forward: trueがないと、設定系統によってはAuthorizationヘッダーがアップストリームに渡る前に除去されることがあります。トークン伝播が必要なら明示してください。
- claim_to_headersは平文ヘッダーです。アップストリームはこのヘッダーを信頼する前に、「Envoyを経由しないトラフィックが不可能であること」をネットワークレベルで保証する必要があります。
Istio — RequestAuthentication + AuthorizationPolicy
Istioは前述のjwt_authnをRequestAuthentication CRDとして、認可をAuthorizationPolicy CRDとして抽象化します。最も重要な事実を最初に強調します。
**RequestAuthentication単独では何もブロックしません。**このリソースは「トークンがあれば検証せよ」という意味に過ぎず、トークンが全くないリクエストは素通りします。ブロックするには、必ずAuthorizationPolicyで「有効な主体(requestPrincipals)を持つリクエストのみ許可」を宣言しなければなりません。この罠によるセキュリティ事故は実際に頻発しています。
ingress gatewayでの一次検証の設定です。
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: ingress-jwt
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: https://keycloak.example.com/realms/prod
jwksUri: https://keycloak.example.com/realms/prod/protocol/openid-connect/certs
audiences:
- api-gateway
forwardOriginalToken: true
outputClaimToHeaders:
- header: x-jwt-sub
claim: sub
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-require-jwt
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
action: DENY
rules:
- from:
- source:
notRequestPrincipals: ['*']
to:
- operation:
notPaths: ['/healthz', '/metrics']
DENY + notRequestPrincipalsのパターンは「有効なトークン主体のないリクエストを拒否する」標準イディオムです。requestPrincipalsの値はissとsubをスラッシュでつないだ形式になります。
サービス層ではより細かい認可をかけます。ordersサービスに「書き込み操作にはorders:writeスコープが必要」を表現すると次のようになります。
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: orders-jwt
namespace: orders
spec:
selector:
matchLabels:
app: orders
jwtRules:
- issuer: https://keycloak.example.com/realms/prod
jwksUri: https://keycloak.example.com/realms/prod/protocol/openid-connect/certs
audiences:
- orders-api
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-authz
namespace: orders
spec:
selector:
matchLabels:
app: orders
action: ALLOW
rules:
# 読み取り: 認証済み主体なら許可
- from:
- source:
requestPrincipals: ['https://keycloak.example.com/realms/prod/*']
to:
- operation:
methods: ['GET']
paths: ['/orders', '/orders/*']
# 書き込み: orders:writeスコープを要求
- from:
- source:
requestPrincipals: ['https://keycloak.example.com/realms/prod/*']
to:
- operation:
methods: ['POST', 'PUT', 'DELETE']
paths: ['/orders', '/orders/*']
when:
- key: request.auth.claims[scope]
values: ['*orders:write*']
ALLOWポリシーが1つでも存在するワークロードは「マッチしないすべてのリクエストを拒否」する動作に変わる点も覚えておくべきです。ポリシーを追加した瞬間、デフォルトがdeny-by-defaultに切り替わります。
JWKSキャッシングと障害モード
JWT検証システムの可用性はJWKSエンドポイントの管理で決まります。障害シナリオを表で整理します。
| シナリオ | 症状 | 対応 |
|---|---|---|
| JWKSエンドポイントの瞬断 | キャッシュ失効後のfetch失敗 → 全面401 | async_fetch + 余裕あるキャッシュTTL、IdPの冗長化 |
| 鍵ローテーション直後 | 新しいkidのトークンがキャッシュミスで401 | IdPは新鍵公開後、猶予期間を置いてから署名を切り替える |
| IdPが旧鍵を即時削除 | 既存トークンがすべて401 | 鍵のretireは最大トークン寿命以上遅延させる |
| ゲートウェイ再起動 + IdPダウン | JWKSの初回fetch失敗 | fail-openの可否をポリシーで決定、ローカルファイルへのフォールバック |
| クロックスキュー | 断続的なexp/nbf検証失敗 | clock_skew_seconds設定 + NTP監視 |
運用上の推奨事項は次のとおりです。
- キャッシュTTLはIdPの鍵ローテーション周期と一体で設計します。例: ローテーション24時間前に新鍵を公開、キャッシュは10分 — これならキャッシュが古くても検証は壊れません。
- Envoyのlocal_jwks(ファイルベース)を非常用に準備しておけば、IdPの完全障害時でも既存の鍵で検証を継続できます。ただし失効済みの鍵が生き残るリスクとのトレードオフです。
- JWKS fetch失敗率、キャッシュヒット率、401比率をメトリクスとして公開しアラートを設定します。Envoyはjwt_authnの統計(denied、jwks_fetch_failedなど)を提供します。
Audience戦略
audクレームは「このトークンが誰のためのものか」を宣言します。戦略の選択肢は3つです。
- 単一audience(ゲートウェイ用に1つ) — 実装は単純ですが、トークンがどのサービスでも再利用可能になり、トークン窃取時の影響範囲が大きくなります。
- サービスごとのaudience — 各サービスが自分のaudienceのみ受け入れます。最も安全ですが、クライアントはサービスごとに異なるトークンを取得する必要があり、サービス間呼び出しにはtoken exchangeが必要になります。
- 階層型(現実的な折衷) — 外部公開API単位でaudienceを分け、内部の細分化はスコープで処理します。ゲートウェイは広域のaudienceを、各サービスは自分が属するAPIのaudience + スコープを検証します。
推奨原則は次のとおりです。
- 少なくとも「この組織のトークンなら全部通す」というaudience未検証の状態は避けます。RFC 9700(OAuth Security BCP)もaudience restrictionを主要な緩和策として明記しています。
- access tokenのaudienceはリソースサーバー基準とし、ID token(aud=クライアント)はAPI呼び出しに使いません。ID tokenをAPIに送るのはよくあるアンチパターンです。
トークン伝播 — 元のトークンの転送 vs Token Exchange
サービスAがユーザーリクエストを受けてサービスBを呼び出すとき、ユーザーコンテキストをどう引き継ぐかという問題です。
パターン1: 元のトークンをそのまま転送(token relay)
Client --(JWT aud=api)--> Gateway --(同じJWT)--> Service A --(同じJWT)--> Service B
- 長所: シンプル、追加のIdP往復なし。
- 短所: audienceを広域にせざるを得ず、トークン窃取時にすべてのサービスが危険にさらされる。トークンの寿命の間、BがAになりすまして他のサービスを呼び出せる。委譲情報がなく監査が不完全。
パターン2: Token Exchange (RFC 8693)
サービスAが受け取ったトークンをIdPに提示し、audienceがBに絞られた新しいトークンと交換します。
curl -s -X POST https://keycloak.example.com/realms/prod/protocol/openid-connect/token \
-d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
-d client_id=service-a \
-d client_secret=SERVICE_A_SECRET \
-d subject_token=ORIGINAL_USER_ACCESS_TOKEN \
-d subject_token_type=urn:ietf:params:oauth:token-type:access_token \
-d audience=service-b
応答で受け取るトークンにはsub(元のユーザー)とともにact(actor)クレームが入り、「service-aがユーザーの代わりに行動している」という委譲チェーンが記録されます。
{
"iss": "https://keycloak.example.com/realms/prod",
"sub": "user-1234",
"aud": "service-b",
"scope": "orders:read",
"act": {
"sub": "service-account-service-a"
},
"exp": 1781234567
}
- 長所: audienceの最小化、委譲チェーンの保存、権限の縮小(scope down)が可能。AIエージェント時代の委譲追跡にも同じメカニズムが使われます。
- 短所: ホップごとのIdP往復(キャッシング必須)、IdP側のexchangeポリシー管理の負担。
実務上の折衷案は「信頼境界を越える箇所(ドメイン間、機密サービスへの進入)でのみexchangeし、同じ信頼境界内ではrelay + mTLS」です。なお、この流れを標準化するtransaction tokensの議論がOAuth WGで進行中であり、次回(SPIFFE/SPIRE)でワークロードの身元とともに再び扱います。
Kong vs APISIX — OIDCプラグイン観点の比較
Envoy系以外で最もよく使われる2つのゲートウェイのOIDC処理方式を比較します。
| 項目 | Kong (openid-connectプラグイン) | APISIX (openid-connect / authz-keycloak) |
|---|---|---|
| 基盤 | nginx/OpenResty + lua-resty-openidc | nginx/OpenResty + lua-resty-openidc |
| ライセンス範囲 | OIDCプラグインはEnterprise | OSSに含まれる |
| 動作モード | 検証(JWT)、セッション(Cookie)、relying party | 検証、relying party、Keycloak認可連携 |
| JWKSキャッシング | 内蔵、ディスカバリーキャッシュ | 内蔵、ディスカバリーキャッシュ |
| 細粒度の認可 | ACL/スコープのプラグイン組み合わせ | authz-keycloakでUMA権限評価を委譲 |
| 宣言的管理 | decK、Kong CRD | APISIX CRD、ADC |
APISIXの設定例は次のとおりです。
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: orders-route
namespace: apps
spec:
http:
- name: orders
match:
hosts:
- api.example.com
paths:
- /orders/*
backends:
- serviceName: orders
servicePort: 8080
plugins:
- name: openid-connect
enable: true
config:
discovery: https://keycloak.example.com/realms/prod/.well-known/openid-configuration
client_id: apisix-gateway
client_secret: GATEWAY_CLIENT_SECRET
bearer_only: true
use_jwks: true
token_signing_alg_values_expected: RS256
audience: orders-api
bearer_only: trueは「ブラウザリダイレクトなしにBearerトークンのみ検証する」APIゲートウェイモードです。Webアプリのセッションまでゲートウェイに処理させたい場合はbearer_onlyを無効にしてrelying partyモードで使います(この場合、ゲートウェイはIAPに近づきます)。
Gateway API時代の認証標準化
KubernetesのGateway APIはIngressの後継としてルーティングの表現を標準化しましたが、認証/認可は長らく実装ごとの拡張(ポリシーCRD)の領域でした。2025〜2026年の流れは次のとおりです。
- Gateway API 1.4でBackendTLSPolicyなどのポリシー添付(Policy Attachment)パターンが定着し、認証フィルターの標準化の議論(HTTPRouteレベルのJWT/extAuthフィルター)がGEP(Gateway Enhancement Proposal)として進行中です。
- それまでの実務は実装ごとのポリシーCRDを使います。Envoy GatewayのSecurityPolicyが代表例です。
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: orders-jwt
namespace: apps
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: orders-route
jwt:
providers:
- name: keycloak
issuer: https://keycloak.example.com/realms/prod
audiences:
- orders-api
remoteJWKS:
uri: https://keycloak.example.com/realms/prod/protocol/openid-connect/certs
claimToHeaders:
- claim: sub
header: x-jwt-sub
同じEnvoyベースでも、Istioは独自CRD、Envoy GatewayはSecurityPolicy、Glooはまた別のCRDを使うという断片化が現在の現実です。ただしすべて下層はjwt_authnフィルターなので、本記事前半の原理理解はすべての実装に通用します。長期的にはHTTPRouteに認証フィルターを標準文法で付与する方向が有力です。
mTLSとJWTの組み合わせ — サービスの身元とユーザーの身元
mTLSとJWTは競合関係ではなく、異なる問いに答える直交的な手段です。
- mTLS (peer identity) — 「このリクエストを送ったワークロードは誰か」。IstioではPeerAuthenticationで強制し、身元はSPIFFE形式のprincipalで表現されます。
- JWT (request identity) — 「このリクエストが代弁する最終ユーザーは誰か」。
両者を組み合わせたAuthorizationPolicyがZero Trustマイクロサービスの標準形です。
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: orders
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-payment-call
namespace: payments
spec:
selector:
matchLabels:
app: payments
action: ALLOW
rules:
- from:
- source:
# 呼び出し元ワークロードを制限(mTLSベースのサービス身元)
principals: ['cluster.local/ns/orders/sa/orders-sa']
# 最終ユーザーのトークンも併せて要求
requestPrincipals: ['https://keycloak.example.com/realms/prod/*']
to:
- operation:
methods: ['POST']
paths: ['/payments']
when:
- key: request.auth.claims[scope]
values: ['*payments:write*']
このポリシーは「ordersサービスアカウントのワークロードが、有効なユーザートークンとpayments:writeスコープを持って、POST /paymentsを呼び出すときのみ許可」を1枚で表現しています。サービスの身元(mTLS)とユーザーの身元(JWT)の二重検証です。
パフォーマンスの観点
JWT検証コストに関する一般的な観察を整理します(絶対値は環境依存のため、自前でのベンチマークを推奨します)。
- RS256の署名検証はリクエストあたり数十マイクロ秒のレベルで、p50レイテンシーにはほぼ影響しません。ES256/EdDSAは検証がより軽く鍵も短いため、2026年の新規構築では好まれます(Keycloak 26.6はEdDSAをサポートします)。
- 本当のコストは署名演算ではなく、JWKS fetchがリクエスト経路に入り込む瞬間です。async_fetchとキャッシュでリクエスト経路から切り離すことが核心です。
- サイドカーでの二次検証による追加遅延は一般にホップあたり1ms未満で、defense in depthの価値に対して受容可能な水準です。
- トークンサイズは見落とされがちなコストです。グループ/権限をすべてクレームに詰め込んで8KBを超えると、ヘッダー上限超過で4xxになる事故がよくあります。クレームは識別子中心に薄く保ち、細粒度の権限はOpenFGAのような専用認可サービスに委譲するのがトレンドです。
トラブルシューティング — 401デバッグのフローチャート
ゲートウェイ401の原因を体系的に絞り込む手順です。
+--------------------------+
| 401が発生 |
+-----------+--------------+
|
トークンはリクエストに載っているか?(ヘッダー確認)
|
+----------- いいえ ---+--- はい ----------+
| |
クライアント/プロキシがAuthorization トークンをデコードして観察(検証ではない)
ヘッダーを欠落/除去?(プロキシチェーン点検) |
+------------------+------------------+
| | |
issは設定と expは過ぎているか? audは設定と
完全一致? (クロックスキュー含む) 一致するか?
| | |
不一致: trailing 期限切れ: 更新 不一致: audience
slash、http/https、 ロジック点検、 マッピングの再設計
realmパスを確認 NTP確認
|
すべて正常なら → 鍵検証の段階を疑う
|
+------------------+-------------------+
| |
トークンヘッダーのkidはJWKSに ゲートウェイはJWKSを
存在するか?(curlでJWKS確認) 取得できているか?
| |
ない: 鍵ローテーション直後の fetch失敗: クラスター定義、
キャッシュ問題 → キャッシュTTL/ DNS、egressポリシー、TLS
ローテーション猶予ポリシー点検 信頼チェーンを点検
|
すべて正常なのに401 → RequestAuthenticationは通過し
AuthorizationPolicyで拒否(403の可能性も)されていないか、
ルールマッチング(パス/メソッド/スコープ)を点検
併せて使う診断コマンド集です。
# 1) トークンのペイロード確認(署名検証なしのデコード)
TOKEN=eyJhbGciOi...
echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .
# 2) JWKSを直接照会 — kid一覧を確認
curl -s https://keycloak.example.com/realms/prod/protocol/openid-connect/certs | jq '.keys[].kid'
# 3) Istioの設定がEnvoyに反映されたか確認
istioctl proxy-config listener deploy/istio-ingressgateway -n istio-system -o json \
| jq '.. | select(.name? == "envoy.filters.http.jwt_authn")'
# 4) Envoyのアクセスログで拒否理由フラグを確認
kubectl logs deploy/istio-ingressgateway -n istio-system | grep -E '401|403' | tail -5
# 5) jwt_authn統計でどの段階で弾かれているか確認
kubectl exec deploy/istio-ingressgateway -n istio-system -- \
pilot-agent request GET stats | grep -E 'jwt_authn|jwks'
401と403の区別も重要です。Istioでは401はRequestAuthentication(トークン自体の問題)、403はAuthorizationPolicy(トークンは有効だが権限不足)で発生します。デバッグの最初の分岐点にしてください。
おわりに
API Gateway層のOIDCトークン検証は、「エッジでふるいにかけ、サービスで再検証する」という原則の上に、Envoy jwt_authnという共通基盤へ収斂しました。まとめると次のとおりです。
- RequestAuthenticationは何もブロックしません。AuthorizationPolicyまでが1セットです。
- 可用性はJWKSキャッシングの設計で決まります。async fetch、TTL、鍵ローテーションの猶予を一体で設計してください。
- audienceは狭く、トークンは薄く、信頼境界を越えるときはtoken exchangeを検討してください。
- mTLS(サービスの身元)とJWT(ユーザーの身元)は組み合わせてこそZero Trustが完成します。
次回は本記事のmTLS側の半分、すなわちSPIFFE/SPIREベースのワークロードアイデンティティを深く扱います。
参考資料
- Envoy jwt_authnフィルター ドキュメント
- Istio Security コンセプト
- Istio RequestAuthentication リファレンス
- Istio AuthorizationPolicy リファレンス
- Kubernetes Gateway API
- Envoy Gateway SecurityPolicy ドキュメント
- RFC 7519 — JSON Web Token (JWT)
- RFC 8693 — OAuth 2.0 Token Exchange
- RFC 9700 — Best Current Practice for OAuth 2.0 Security
- RFC 8725 — JWT Best Current Practices
- OpenID Connect Core 1.0
- OAuth 2.1 draft (draft-ietf-oauth-v2-1)
- Keycloak ドキュメント
- Apache APISIX openid-connectプラグイン
- Kong OpenID Connectプラグイン
- OpenFGA ドキュメント