Claude apps gatewayのprivate_key_jwt設定 — Entraで証明書認証
Entra IDのように証明書資格情報でOAuthクライアントを認証するIdPでは、client_secretの代わりにclient_assertionを渡します。設定、起動拒否の条件、証明書更新の順序まで。
Claude apps gatewayのIdPがOAuthクライアントに証明書資格情報を使わせる場合は、oidc.token_endpoint_auth_method: private_key_jwtを指定し、client_secretの代わりにclient_assertionブロックで秘密鍵と証明書を渡します。Microsoft Entra IDが代表例です。ゲートウェイ側のClaude Codeはv2.1.284以降が必要です。
秘密文字列を一切送らず、サインイン時とセッション更新時に、証明書の秘密鍵で署名した短命のJWTでトークンエンドポイントへ認証する方式です。設定は数行ですが、鍵の形式や書き方を間違えるとゲートウェイが起動しません。ここでは手順と、起動拒否の条件、証明書の入れ替え順を扱います。
client_secretとprivate_key_jwtは何が違うのか
どちらもゲートウェイがIdPのトークンエンドポイントに自分を証明する方法です。違いは、証明に使う材料です。
トークンエンドポイントへの認証方式
client_secret系
client_secret_basicかclient_secret_post。どちらを使うかは、token_endpoint_auth_methodを省くとIdPが示す対応方式から自動で選ばれます。
private_key_jwt
秘密は送りません。サインインのたび、またセッション更新のたびに、秘密鍵で署名した短命のJWTを送ります。
JWTの署名はRS256です。ヘッダーにはkidではなくx5tとx5t#S256の拇印(サムプリント)が入るため、IdPは登録済みの証明書を拇印で探せる必要があります。Entra IDの証明書資格情報はこの条件に合う、というのが設定ページの前提です。
token_endpoint_auth_methodの指定が要るのは、証明書方式を選ぶときだけです。省いたままだと、ゲートウェイはclient_secret系の2方式のどちらかを選ぶので、client_assertionを書いても使われません。
設定の手順
証明書認証への切り替えは、鍵と証明書の作成、IdPへの証明書登録、gateway.yamlへの記述、再起動後の拇印照合の4段階です。途中を飛ばしたときの失敗は、各段階の末尾に書きます。
鍵と証明書を作る
秘密鍵はパスフレーズなしで、PKCS#8かPKCS#1のPEM形式にします。2048ビット未満の鍵や、この条件を外れた鍵ではゲートウェイが起動しません。次のコマンドは、有効期間1年の自己署名証明書つきで条件に合う鍵を作ります。
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout idp-client.key -out idp-client.crt \
-days 365 -subj "/CN=claude-gateway"カレントディレクトリにidp-client.keyとidp-client.crtができます。両方をゲートウェイが読める場所に置くかマウントします。以降の例は/etc/gateway/を使います。
ここで作る証明書の有効期間は-days 365の1年です。期限はあとで起動ログに表示されるので、更新の予定を立てる起点になります。
証明書だけをIdPへ登録する
IdPのアプリ登録にアップロードするのは証明書(.crt)です。秘密鍵(.key)は渡しません。
この登録を忘れたままゲートウェイを切り替えると、IdPには署名の検証に使える証明書がありません。IdPは拇印で登録済みの証明書を探すので、ゲートウェイが署名に使う証明書と同じものが、IdP側に載っている必要があります。
gateway.yamlに書く
Entraのテナントに証明書で認証するoidcブロックは、次の形になります。
oidc:
issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
client_id: <application-id>
token_endpoint_auth_method: private_key_jwt
client_assertion:
private_key_pem: ${file:/etc/gateway/idp-client.key}
certificate_pem: ${file:/etc/gateway/idp-client.crt}private_key_pemとcertificate_pemに入れるのは、ファイルのパスではなくPEMの中身です。マウントしたファイルは${file:/path}で読み込みます。この記法は、フィールドの値全体がその参照であるときだけ展開されます。長い文字列の途中に埋め込む使い方はできません。
client_secretは書きません。両方あるとゲートウェイは起動を拒否します。client_secretが必須になるのは、token_endpoint_auth_methodがprivate_key_jwtでない場合だけです。逆にclient_assertionは、private_key_jwtを選んだときに必須のブロックです。
${file:}と${VAR}の違い
gateway.yamlに秘密を直書きせず、起動時に解決させる記法は2つあります。
| 記法 | 展開されるもの | 展開の条件 |
|---|---|---|
${VAR} | 展開されるもの環境変数VARの値 | 展開の条件未定義だと起動に失敗する |
${file:/path} | 展開されるもの絶対パスのファイルの中身(前後の空白は除かれる) | 展開の条件フィールドの値全体が参照であること。長い文字列の途中では展開されない |
PEMは複数行のファイルなので、鍵と証明書は${file:}で読ませるのが公式の例の形です。${file:}が値全体でなければならない点は、private_key_pemに前置きの文字を足したくなったときに引っかかります。
Entraの接続では、ほかに2点がoidcブロックの設定になります。Entraはグループ名ではなくオブジェクトIDを返すため、ポリシー側にはGUIDを書きます。可読な名前で扱いたいときはアプリロールを使う手があり、その場合の値はrolesクレームに出るのでgroups_claim: rolesを指定します。
再起動して拇印を照合する
再起動し、起動ログに次の行が出ているかを見ます。
[gateway] 2026-10-01T23:07:40.512Z info oidc: client authentication private_key_jwt; certificate CN=claude-gateway, SHA-1 thumbprint DE92821854EE8BAA1D98C758FAA04AABE80B9F57, expires Oct 1 23:07:31 2027 GMTここに出るSHA-1拇印を、IdPがアップロード済みの証明書として表示する拇印と見比べます。一致すれば、IdPが探す証明書と、ゲートウェイが署名に使う証明書は同じものです。再起動の前に手元で拇印と期限を確かめておく場合は、証明書ファイルに対して次のコマンドが使えます。
openssl x509 -in idp-client.crt -noout -enddate -fingerprint -sha1notAfterの日付が起動ログのexpiresに、SHA1のフィンガープリントがSHA-1拇印にあたります(表記は区切りのコロンの有無などが違います)。ここで期限や拇印が想定と違えば、ゲートウェイを動かす前に気づけます。最後に開発者の1人にゲートウェイ経由でサインインしてもらうと、IdPが証明書を受け入れたことまで確かめられます。
起動しない・失敗するときの切り分け
証明書方式のエラーは、大半が起動時の検査で出ます。設定ページに書かれた拒否条件を、症状から引ける形にまとめます。
| 症状 | 条件 |
|---|---|
| ゲートウェイが起動しない | 条件client_secretとprivate_key_jwtを同時に設定している |
| ゲートウェイが起動しない | 条件秘密鍵が暗号化されている、2048ビット未満、PKCS#8/PKCS#1のPEMではない |
| ゲートウェイが起動しない | 条件certificate_pemにチェーンの残りも入っている(単一のPEM証明書でなければならない) |
| ゲートウェイが起動しない | 条件certificate_pemの公開鍵がprivate_key_pemと対応していない |
| 起動はするが、サインインと更新が失敗する | 条件証明書の期限切れ、またはまだ有効期間に入っていない(起動時に警告ログが出る) |
最後の行だけは、起動を止めません。警告ログを見逃すと、開発者側の症状だけが先に出ます。期限の控え方は、後半の「期限切れを防ぐ監視の置き方」にまとめました。
サインイン後のエラー側の切り分けは、gatewayのログインエラー3種にまとめています。
証明書を更新する順序
ゲートウェイは鍵と証明書を起動時に1度だけ読みます。ファイルを差し替えただけでは反映されず、再起動が必要です。トークン要求がIdPの知らない証明書を提示する瞬間を作らないために、次の順に進めます。
- 新しい証明書を、古いものと並べてIdPへ登録する
gateway.yamlが読む鍵と証明書のファイルを差し替え、ゲートウェイを再起動する- 全レプリカの再起動が終わったら、古い証明書をIdPから外す
複数レプリカでも、ローリング再起動で足ります。IdPには手順3まで両方の証明書があるので、新旧のレプリカが混在しても署名は通ります。ローリング再起動中の挙動はデプロイと運用の解説で扱っています。
手順1と3の間に時間がかかっても、IdPに両方の証明書が登録されている間は、新旧どちらの証明書で署名しても通ります。失敗の型は逆の2つです。古い証明書を先に外す、あるいは新しい証明書の登録前にゲートウェイを再起動すると、IdPが知らない証明書でトークン要求が届きます。
期限切れを防ぐ監視の置き方
証明書の期限が切れると、ゲートウェイは起動はしますが、サインインとセッション更新が失敗します。警告ログが出るのは起動時で、鍵と証明書は起動時に1度しか読まれません。期限を知らせてくれる仕組みは、運用側で用意する形になります。
控える日付は、起動ログのoidc: client authenticationの行にあるexpiresです。導入した日に、この日付を次の3つに写しておきます。
- 更新作業の開始日:
expiresの何日か前。手順1のIdP登録から手順3の削除まで、変更承認や全レプリカの再起動を含めて終わる日数を見込みます。30日前は一つの目安です - 古い証明書をIdPから外す日: 手順3の予定日
- 更新後の確認日: 再起動後の起動ログで、新しい
expiresと拇印を見る日
更新後に控え直すのも同じ行です。拇印とexpiresが新しい証明書のものに変わっていれば、差し替えが反映されています。
IdP通信とプロキシの関係
証明書認証と無関係に見えて、実際には同じoidcブロックの設定が絡む場面があります。ゲートウェイからIdPへの通信、つまりディスカバリー、JWKS、トークン、userinfoは、oidc.use_proxy: trueを設定しない限り、プロキシを通さず直接出ます。プロキシ越しにしか外へ出られない環境では、トークンエンドポイントへの証明書付き要求もこの経路に乗ります。use_proxyはv2.1.227以降の項目で、HTTPS_PROXYかHTTP_PROXYの値を使い、NO_PROXYに載せた宛先は直接つなぎます。社内CAを使うIdPでは、PEMの中身を渡すca_cert_pem(${file:/etc/gateway/idp-ca.pem}の形で読み込めます)で、IdPへの通信だけ信頼するCAを置き換えられます。Azure ADからADFSへフェデレーションする構成のように、認可リクエストが別のホストを経由するIdPでは、form_action_originsに経由先のオリジンを足す設定もあります。条件はproxy経由だけの環境での設定に詳しく書きました。
既存のSSO構成との関係
private_key_jwtが変えるのは、ゲートウェイがIdPへ自分を証明する部分だけです。allowed_email_domainsやallowed_groups、groups_claimなど、誰がサインインできるかを決める設定はそのまま使います。Entraではグループ名ではなくオブジェクトIDが出るため、managed.policies.match.groupsにはGUIDを書きます。テナントがグループをgroupsでなくrolesに出すときは、oidc.groups_claim: rolesを指定します。
設定項目の全体はgateway.yamlのリファレンスに、IdPごとの違いはSSOのIdP比較にあります。
まとめ
証明書認証への切り替えで確認するのは、3点です。client_secretを残していないこと、certificate_pemが単一の証明書であること、起動ログの拇印がIdP側と一致すること。更新時は、新しい証明書の登録、再起動、古い証明書の削除の順を守れば、サインインを止めずに入れ替えられます。