MS Entra ID External ID(CIAM)で JWT の kid が JWKS に存在しない問題:署名検証がランダム失敗する原因と ciamlogin.com への切り替え手順

MS Entra ID の External ID(CIAM)環境でログインが突然不安定になり、「The signature key was not found」などの署名検証エラーが出る場合、原因は JWKS(公開鍵一覧)の参照先ミスかもしれません。kid 不一致が起きる仕組みと、ciamlogin.com 側へ揃えて解決する手順を実例ベースでまとめます。

目次

現象:JWT の kid が JWKS に存在せず、署名検証が失敗する

External ID(CIAM)テナントで発行された JWT をアプリ側で検証していると、次のような現象に遭遇することがあります。

  • 同じ環境・同じアカウントでも、直前は成功していたのに数%〜20% 程度の確率で失敗する
  • しばらくすると失敗率が上がり、最終的に 100% 失敗してログイン不能になる
  • エラーは invalid_token / The signature key was not found / IDX10501 など「kid が見つからない」系で止まる

典型例は「トークンのヘッダにある kid(Key ID)が、検証に使っている JWKS に存在しない」パターンです。

確認ポイント例意味
JWT ヘッダ{"alg":"RS256","kid":"mUG6vYnRlofuLxciZru7Sptf6NM","typ":"JWT"}署名に使った鍵を kid で特定する
JWKS(公開鍵一覧)https://login.microsoftonline.com/<tenant>/discovery/keysアプリが「この一覧のどれかで検証できるはず」と見に行く
失敗時の差分JWKS に mUG6vYnRlofuLxciZru7Sptf6NM が存在しない結果として署名検証ができず失敗する

ここで重要なのは、JWT のバージョン(v1 / v2)だけでは説明できないケースがあることです。v2 トークンでも発生報告があり、「v1 が原因」という推測は決定打になりません。根本は別にあります。

結論:External ID(CIAM)では ciamlogin.com 側のメタデータ/JWKS を使う

External ID(CIAM)系のテナントでは、login.microsoftonline.com 側の JWKS ではなく、ciamlogin.com 側の JWKS(公開鍵)とメタデータ(openid-configuration)を基準に検証することで解決する事例が複数あります。

実際に問題の kid が ciamlogin.com 側の keys には存在し、そこに切り替えると「ランダム失敗 → 100% 失敗」状態が解消した、という流れです。

まずは応急処置:JWKS(keys)の参照先を ciamlogin.com に変更

最短で効く対処は、JWKS URL を次の形式に切り替えることです。

  • https://<tenant>.ciamlogin.com/<tenant>/discovery/v2.0/keys

ただし、ライブラリやフレームワークによっては「JWKS だけ差し替える」方法がうまくいかないことがあります。なぜなら、多くの実装は Authority(メタデータ取得元)から jwks_uri を自動で決めるからです。

より根本対応:Authority / Issuer(検証元)を CIAM に揃える

安定運用の観点では、Authority(メタデータ取得元)を CIAM のものに統一するのが安全です。例として、次の形式が挙げられます。

  • https://<tenant-subdomain>.ciamlogin.com/<tenant-id>/v2.0

この Authority を使って /.well-known/openid-configuration(OpenID Connect メタデータ)を取得し、そこに含まれる jwks_uri を使って検証する、という「王道の手順」に戻すのがポイントです。

最速で原因を断定する:iss(発行者)を確認する

いちばん確実な切り分けは、トークンの iss(Issuer)クレームを確認することです。検証側は、基本的に iss と同じ系統のメタデータ/JWKS を参照すべきです。

トークンの iss検証に使うべき系統やること
https://...ciamlogin.com/...CIAM(ciamlogin.com)Authority / MetadataAddress / jwks_uri を ciamlogin 側へ
https://login.microsoftonline.com/... などEntra ID(login.microsoftonline.com)従来の AAD 構成で OK(ただし External ID の場合は要注意)

もし iss が ciamlogin.com なのに、検証ロジックが login.microsoftonline.com/<tenant>/discovery/keys を見に行っているなら、鍵セットが一致しないため kid 不一致が起きます。

なぜ「ランダムに失敗」して「最終的に 100% 失敗」へ悪化するのか

この現象は、単に「鍵がない」だけでなく、運用上のタイミング差で“たまたま通る瞬間がある”ことがポイントです。よくある説明は次の通りです。

  • CIAM 側では複数の署名鍵(キー ローテーション)が存在する
  • アプリ側は誤って別ドメイン(login.microsoftonline.com)の JWKS を参照している
  • 初期は「偶然、両方に存在する鍵」や「古い鍵」で署名されたトークンに当たり、たまに成功する
  • 鍵が切り替わる・キャッシュが更新されるなどのタイミングで、成功の余地がなくなり 100% 失敗へ移行する

つまり、ランダム失敗は「ネットワーク不安定」ではなく、参照している鍵セットがそもそも違うことを示している可能性が高い、ということです。

チェックリスト:3点セット(iss / metadata URL / jwks_uri)が揃っているか

切り分けで見るべき要素は、だいたい次の 3つに集約されます。

項目確認方法期待値(CIAM の場合)
① トークンの issJWT をデコードして payload を確認...ciamlogin.com... を含む
② 検証で参照するメタデータ URLAuthority / MetadataAddress の設定値を確認https://...ciamlogin.com/.../v2.0 系
③ メタデータ内の jwks_uriopenid-configuration を取得して jwks_uri を確認https://...ciamlogin.com/.../discovery/v2.0/keys 系

この 3つが「同一系統(ciamlogin か login.microsoftonline か)で統一」されていれば、kid 不一致はほぼ解消します。逆に、ここが混ざると高確率で再発します。

CIAM と従来 Entra ID の「参照先」違いを整理する

External ID(CIAM)を触り始めたばかりだと、Entra ID(旧 Azure AD)と同じ感覚で login.microsoftonline.com を前提にしてしまいがちです。ここを整理しておくと、再発防止にもつながります。

要素従来 Entra ID(例)External ID / CIAM(例)注意点
発行者(iss)のドメインlogin.microsoftonline.comciamlogin.comまず iss を見て「どちらの系統か」を確定する
メタデータ(openid-configuration)https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configurationhttps://<tenant-subdomain>.ciamlogin.com/<tenant-id>/v2.0/.well-known/openid-configurationここから jwks_uri を辿るのが正攻法
JWKS(公開鍵).../discovery/v2.0/keys などhttps://<tenant>.ciamlogin.com/<tenant>/discovery/v2.0/keysURL を直書きするなら、ドメインの混在を絶対に避ける

実務上は「openid-configuration を取りに行って、その jwks_uri で検証する」形に寄せておけば、パスの細部(tenant-id を使うか tenant 名を使うか等)で悩む時間が激減します。

実装例:主要スタック別の設定ポイント

ここからは「WordPress 記事としてコピペできる」ことを優先しつつ、実運用で刺さりやすい設定だけを抜き出します。細部はプロジェクトの構成に合わせて調整してください。

.NET(ASP.NET Core / JwtBearer)

ポイントは Authority を ciamlogin にすることです。多くの場合、これだけでメタデータと JWKS が自動的に揃います。

// Program.cs / Startup.cs の一例(概念例)
services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://<tenant-subdomain>.ciamlogin.com/<tenant-id>/v2.0";
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            // 例:API の Audience(アプリ登録の Application (client) ID や api://...)
            ValidAudience = "<your-api-audience>",
            ValidateIssuer = true
        };
    });

もし既存設定で https://login.microsoftonline.com/ を Instance にしている場合、CIAM ではそれがズレの起点になります。Authority を変えたら、Issuer の検証(ValidIssuer / ValidIssuers)も同じドメイン系に寄せるのが安全です。

Node.js(jsonwebtoken + jwks-rsa の例)

「jwksUri だけ手動で差し替える」運用になりがちなスタックです。CIAM の keys を指すようにします。

const jwt = require("jsonwebtoken");
const jwksClient = require("jwks-rsa");

const client = jwksClient({
jwksUri: "https://.ciamlogin.com//discovery/v2.0/keys"
});

function getKey(header, callback) {
client.getSigningKey(header.kid, function(err, key) {
if (err) return callback(err);
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}

jwt.verify(token, getKey, {
algorithms: ["RS256"],
issuer: "https://.ciamlogin.com//v2.0",
audience: ""
}, function(err, decoded) {
// err が "signing key was not found" の場合は jwksUri/issuer の系統を再確認
});

Node では「kid が見つからない → JWKS を強制再取得」というリカバリを入れることがありますが、参照先ドメインが間違っていると何度再取得しても直りません。まず issuer/jwksUri を揃えるのが先です。

Java(Spring Security Resource Server)

Spring Security は issuer-uri を設定するとメタデータを辿って JWKS を取得します。だからこそ、issuer を CIAM にしておくのが安定です。

# application.yml の例
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: "https://<tenant-subdomain>.ciamlogin.com/<tenant-id>/v2.0"

逆に、ここが login.microsoftonline.com になっていると、取得する jwks_uri が AAD 側になり、kid 不一致が出ます。

「JWKS だけ変えたのに直らない」ケースの落とし穴

コメントや現場で多いハマりどころを、あえて先回りして書きます。

ライブラリが結局 login.microsoftonline.com を参照している

多くのライブラリは Authority(メタデータ)→ jwks_uri を自動決定します。設定項目が複数ある場合、片方だけ ciamlogin にしても、別の場所が login.microsoftonline.com のまま残ると再発します。

  • API の JWT 検証は ciamlogin にしたが、別コンポーネント(Graph 用のクライアント等)が login.microsoftonline を前提にしている
  • フロントエンド(MSAL)とバックエンド(Resource Server)で Authority が食い違っている
  • マルチテナント設定(common / organizations)を使っていて issuer の想定がズレている

Graph /me など別の通信で「別 Authority」を暗黙に使っている

トークン検証は通ったのに、別の API 呼び出しで 401 になり「やっぱり login.microsoftonline.com が必要では?」と混乱することがあります。この場合は、トークンの種類(ID トークン / アクセストークン)と、そのトークンが向けられているリソース(aud)を分けて考えるのがコツです。

  • JWT 検証は「そのトークンの iss と鍵」に合わせる
  • 呼び出す先の API は「その API が期待する aud / scope / 権限」に合わせる

「トークン検証の Authority」と「別 API クライアントの Authority」が同じとは限りません。ただし、今回の kid 不一致は検証しているトークンの iss とメタデータ取得元がズレていることが本質なので、まずそこを直してから、API 呼び出し側の要件を整理するとスムーズです。

Issuer 検証を雑に無効化してしまう

「とりあえず通す」ために ValidateIssuer=false にしてしまうと、別の問題(想定外の発行者からのトークン受け入れ)を生みます。CIAM の場合も、Issuer を正しく揃えれば通常は無効化せずに解決できます。どうしても例外が必要なら、ValidIssuers を明示して許可リストで運用する方が安全です。

トラブルシューティング手順(現場でそのまま使える)

「何を見ればいいか分からない」状態でも、次の手順でほぼ原因に辿り着けます。

手順やること判断基準
1失敗した JWT のヘッダから kid を控えるkid が毎回同じか、複数あるかで状況を把握
2同じ JWT の payload から iss を確認ciamlogin.com なら CIAM 系に寄せる
3検証で参照しているメタデータ URL(Authority)を確認iss とドメインが一致しているか
4openid-configuration の jwks_uri を確認jwks_uri が ciamlogin になっているか
5jwks_uri の返す JWKS に kid が存在するか確認存在しなければ参照先が間違い or キャッシュ問題

ポイントは、失敗したその瞬間のトークンで確認することです。成功したトークンだけを見ても、たまたま古い鍵で署名されていて「見かけ上問題なし」になっている場合があります。

運用で再発させないためのベストプラクティス

対処して終わりにすると、別の形でまた詰まります。CIAM に限らず JWKS を使う JWT 検証では、次の方針が堅実です。

メタデータ主導(issuer-uri / authority)に寄せる

可能なら「JWKS URL を直書き」より、issuer(または authority)を 1箇所だけ設定し、メタデータに従う構成が最も壊れにくいです。理由は、鍵ローテーションやエンドポイント変更があっても追従しやすいからです。

kid ミス時のリフレッシュ戦略を入れる

CDN やキャッシュの都合で、鍵が更新された直後に一時的に取りこぼすことがあります。実装可能なら、次のような戦略が有効です。

  • kid が見つからない場合だけ JWKS を再取得(強制リフレッシュ)して再検証する
  • JWKS のキャッシュ TTL を長くしすぎない(ただし短すぎると負荷増)
  • リトライは回数と間隔を制御し、無限ループにしない

ただし繰り返しになりますが、参照先ドメインが誤っているとリフレッシュしても解決しません。まず Authority を正すのが前提です。

監視:署名検証エラーを「障害の前兆」として扱う

失敗率が数%の段階は「まだログインできる」ため見過ごされがちですが、この問題は放置すると 100% 失敗に進みやすい類です。次のメトリクスを監視に入れると、早期に気づけます。

  • The signature key was not found の発生率
  • 認証ミドルウェアが吐く invalid_token の比率
  • kid の種類の増減(新しい kid の出現)

よくある質問

v1 トークンを v2 にすれば直りますか?

外形上は v1/v2 の切り替えが効きそうに見えますが、今回の「kid が JWKS に存在しない」症状は、v2 でも起き得ます。v1/v2 ではなく、トークンの iss と検証のメタデータ取得元が一致しているかを最優先で確認してください。

とりあえず ValidateIssuer=false で回避していいですか?

推奨しません。署名検証が通ってしまう状況では、Issuer 検証はセキュリティ境界の一部です。CIAM の Authority に揃えれば、通常は issuer を正しく検証したまま解決できます。

サポートに連絡すべきですか?

環境固有の事情(カスタム ドメイン、プロキシ、特殊なキャッシュ、ライブラリの制約など)が絡むと、最終的にサポートの助けが必要になることもあります。ただし、実際に直った報告としては ciamlogin 側のメタデータ/JWKS(および Authority の見直し)が最も再現性の高い解決策です。

まとめ:CIAM は「発行者と検証元のドメイン統一」が最重要

External ID(CIAM)で「JWT の署名鍵(kid)が JWKS に出てこず、署名検証がランダムに失敗する」問題は、ほとんどの場合、トークンの発行者(iss)と、検証で参照しているメタデータ/JWKS のドメインが揃っていないことが原因です。

  • まずはトークンの iss を見る
  • iss が ciamlogin.com なら Authority / MetadataAddress / jwks_uri を ciamlogin 側に統一する
  • 「JWKS だけ差し替え」ではなく、できるだけ Authority から辿る構成に寄せる

この 3点を押さえるだけで、謎のランダム失敗が嘘のように消えることが多いはずです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次