Azure Static Web Apps と Entra External ID(CIAM)移行後に認証がループする原因と対処法

Azure Static Web Apps で Azure AD (login.microsoftonline.com) から Microsoft Entra External ID (CIAM / ciamlogin.com) に切り替えた途端、ログインは成功しているのにアプリ側ではサインイン画面に戻され続ける――そんな「認証ループ」で詰まっていませんか。本記事では、原因と具体的な解決手順を、staticwebapp.config.json のサンプルとともに詳しく解説します。

目次

Azure Static Web Apps と Entra External ID (CIAM) 移行後に起きる認証ループとは

まず、どんな現象が発生しているかを整理します。

  • 従来:Azure AD テナント(login.microsoftonline.com)に対して、SWA の組み込み azureActiveDirectory プロバイダーで正常にログインできていた。
  • 移行後:Microsoft Entra External ID (CIAM) テナント(*.ciamlogin.com)に切り替えたところ、
    • サインイン画面で ID/パスワード入力
    • MFA も成功と表示
    • しかしアプリ側には戻らず、再度サインイン画面にリダイレクトされる
    • このサイクルが永遠に続く(リダイレクト・ループ)
  • Azure ポータルのログを見ると「ログイン成功」と出ているため、どこが悪いのか分かりにくい。

多くの方は、staticwebapp.config.json の openIdIssuer だけを CIAM のエンドポイントに変える、という対応を取ります。

{
  "auth": {
    "identityProviders": {
      "azureActiveDirectory": {
        "registration": {
          "openIdIssuer": "https://<tenant-name>.ciamlogin.com/<tenant-id>/v2.0",
          "clientIdSettingName": "AZURE_CLIENT_ID",
          "clientSecretSettingName": "AZURE_CLIENT_SECRET"
        }
      }
    }
  }
}

しかし、これだけでは CIAM では動きません。理由は、CIAM テナント (ciamlogin.com) が Azure Static Web Apps の「組み込み」Azure AD プロバイダーではサポートされておらず、カスタム OpenID Connect プロバイダーとして構成する必要があるためです。

なぜ Azure AD では動いていたのに CIAM でループするのか

ポイントは以下の 2 つです。

  1. 組み込み azureActiveDirectory プロバイダーは、Microsoft Entra ID(従業員向けテナント)向けに最適化されている。
  2. CIAM テナントは ciamlogin.com ドメインを使う別系統のサービスであり、トークンの発行元 (issuer) やクレームの扱いが一部異なる。

Azure Static Web Apps の公式ドキュメントでも、カスタム OpenID Connect プロバイダーを使う場合は auth.identityProviders.customOpenIdConnectProviders を使うことが明記されています。

Microsoft Q&A でも、CIAM テナントで組み込み azureActiveDirectory を使おうとして認証ループに陥った事例が報告されており、公式モデレーターから「CIAM テナント (ciamlogin.com) は built-in AAD プロバイダーではサポートされない。customOpenIdConnectProviders で構成してほしい」と回答されています。

つまり、設定ミスというよりは構成方式そのものが間違っている、というのが最大の原因です。

項目従来 Azure AD テナントEntra External ID (CIAM) テナント
Issuer ドメインlogin.microsoftonline.com<tenant>.ciamlogin.com
SWA 側プロバイダーazureActiveDirectory (組み込み)customOpenIdConnectProviders (カスタム)
ログイン URL 例/.auth/login/aad/.auth/login/ciam (ラベルに依存)
典型的な失敗パターンIssuer URL のタイポ等組み込み AAD のまま CIAM を指す → ループ

解決の全体像

大枠の解決ステップは次のとおりです。

  1. staticwebapp.config.json で CIAM をカスタム OIDC プロバイダーとして定義する。
  2. CIAM テナント側で リダイレクト URI(コールバック)を /.auth/login/プロバイダー名/callback まで含めて完全一致で登録する。
  3. /.auth/* へのアクセスを anonymous で許可し、それ以外のパスを authenticated に制限する。
  4. カスタムドメインを使う場合は staticwebapps.net とカスタムドメインの両方のコールバック URI を登録する。
  5. /.auth/login/ciam?post_login_redirect_uri=/ などで動作確認し、/.auth/me でクレームが取れることを確認する。

順番に見ていきます。

staticwebapp.config.json の最小構成例

CIAM を使った最小構成の例です。プロバイダー名として ciam を選んでいますが、任意のラベルを付けられます。このラベル名がログイン URL およびコールバック URL にそのまま反映されます。

{
  "routes": [
    {
      "route": "/.auth/*",
      "allowedRoles": [ "anonymous" ]
    },
    {
      "route": "/*",
      "allowedRoles": [ "authenticated" ]
    }
  ],
  "responseOverrides": {
    "401": {
      "statusCode": 302,
      "redirect": "/.auth/login/ciam"
    }
  },
  "auth": {
    "identityProviders": {
      "customOpenIdConnectProviders": {
        "ciam": {
          "registration": {
            "clientIdSettingName": "AZURE_CLIENT_ID",
            "clientSecretSettingName": "AZURE_CLIENT_SECRET",
            "openIdConnectConfiguration": {
              "wellKnownOpenIdConfiguration": "https://&lt;tenant-name&gt;.ciamlogin.com/&lt;tenant-id&gt;/v2.0/.well-known/openid-configuration"
            }
          }
          /* "login": { "scopes": ["openid","profile","email"] } は省略可(後述) */
        }
      }
    }
  }
}

ここでのポイントは次の通りです。

  • /.auth/* を anonymous にしていること。
    • このパスを塞いでしまうと、SWA 自身が認証フローを完結できず、結果として認証ループになります。
  • 401 は /.auth/login/ciam に 302 リダイレクトしていること。
    • 未認証ユーザーのアクセスを CIAM ログインに誘導します。
  • customOpenIdConnectProviders に ciam というラベル名でプロバイダーを登録していること。
    • ログイン URL は /.auth/login/ciam、コールバックは /.auth/login/ciam/callback になります。
  • wellKnownOpenIdConfiguration は Issuer URL + /.well-known/openid-configuration
    • CIAM の場合は https://<tenant-name>.ciamlogin.com/<tenant-id>/v2.0/.well-known/openid-configuration という形になります。

clientSecretSettingName の位置について

上の例では、clientSecretSettingName を registration 直下に書いています。この形式は、ほかの組み込みプロバイダー(GitHub、Google 等)でも採用されているシンプルな形です。

Azure の公式ドキュメントには、カスタム OIDC プロバイダーの例として、以下のように clientCredential オブジェクトを挟むスタイルも記載されています。

"registration": {
  "clientIdSettingName": "MY_PROVIDER_CLIENT_ID",
  "clientCredential": {
    "clientSecretSettingName": "MY_PROVIDER_CLIENT_SECRET_APP_SETTING_NAME"
  },
  "openIdConnectConfiguration": {
    "wellKnownOpenIdConfiguration": "https://&lt;PROVIDER_ISSUER_URL&gt;/.well-known/openid-configuration"
  }
}

どちらの書き方もサポートされていますが、

  • プロパティ名のスペルミス
  • 余計な階層を作ってしまう(例:clientCredentials など、微妙なタイポ)

といったミスがあると、SWA がクライアントシークレットを解決できず、トークン検証に失敗 → 認証ループという症状につながります。特に CIAM を初めて試すときは、まずはシンプルな clientSecretSettingName 直下のパターンで動作確認することをおすすめします。

ログイン URL とコールバック URL の対応表

設定上のラベル名ログイン URLコールバック URL
"ciam"/.auth/login/ciam/.auth/login/ciam/callback
"aadb2c"(B2C の例)/.auth/login/aadb2c/.auth/login/aadb2c/callback

この「ラベル名 = パスの一部」というルールを忘れて、CIAM 側に /aad/callback を登録してしまうケースが非常に多いので注意してください。

CIAM(Entra External ID)側の設定ポイント

次に、Microsoft Entra External ID (CIAM) テナント側での設定を整理します。

アプリ登録(Web アプリとして登録)

  1. CIAM テナントの Azure ポータルで「アプリの登録」を作成します(Web アプリ)。
  2. プラットフォームに 「Web」 を追加し、リダイレクト URI を以下のように設定します。
    • https://<your-app>.staticwebapps.net/.auth/login/ciam/callback
    • カスタムドメインを使う場合:
      • https://<your-custom-domain>/.auth/login/ciam/callback
  3. 「アプリケーション (クライアント) ID」とクライアントシークレットを控え、SWA のアプリ設定に
    • AZURE_CLIENT_ID
    • AZURE_CLIENT_SECRET
    として登録します。

CIAM で Google や Microsoft アカウントなど外部 ID プロバイダー(IdP)とフェデレーションしている場合は、各 IdP 側の開発者ポータルでも同じコールバック URI を登録する必要があります(CIAM のアプリ単体だけ登録しても戻ってきません)。

スコープ設定について

OpenID Connect の標準スコープである openid, profile, email は、Azure Static Web Apps のカスタム OIDC プロバイダーで既定で要求されます。

そのため、staticwebapp.config.json の login.scopes に明示的に書かなくても基本的には問題ありません。トラブルシューティングの観点からも、まずは余計なカスタマイズを入れずに既定動作で動かしてみるのが安全です。

"login": {
  "scopes": ["openid", "profile", "email"]
}

上記のように指定しても悪さをすることはほぼありませんが、「スコープを明示すればループが直る」類の問題ではないことは押さえておきましょう。

Issuer / メタデータの確認

CIAM の OIDC メタデータは、以下の URL から取得できます。

https://&lt;tenant-name&gt;.ciamlogin.com/&lt;tenant-id&gt;/v2.0/.well-known/openid-configuration

ブラウザや curl でこの URL を開き、返却される JSON 内の issuer が、SWA 側の openIdConnectConfiguration の Issuer と完全一致しているか確認します。

認証ループの典型原因とチェックリスト

ここからは、よくある「ループ」の原因と、その切り分けポイントを整理します。

1. プロバイダー種別の不一致

最も多いのが、組み込み azureActiveDirectory プロバイダーのまま CIAM テナントを指しているパターンです。

  • 症状:
    • MFA まで成功するが、アプリに戻った瞬間にまたサインイン画面へ。
    • SWA のログでは「ログイン成功」と見えることもある。
  • 対処:
    • azureActiveDirectory のブロックを削除し、customOpenIdConnectProviders.ciam を定義し直す。

2. コールバック URI とラベル名の不一致

staticwebapp.config.json では "ciam" というラベル名を使っているのに、CIAM 側では /.auth/login/aad/callback を登録してしまっている、というケースです。

この場合、

  • CIAM 側からは /aad/callback にトークンを返しているのに、
  • SWA 側は /ciam/callback を待っている

という食い違いが起きます。結果的に トークンが正しく処理されず、未認証扱い → 401 → 再ログインとなり、ループになります。

3. /.auth/* をブロックしてしまっている

次に多いのが、routes で「全て認証必須」にした結果、/.auth/* まで塞いでしまうパターンです。

"routes": [
  {
    "route": "/*",
    "allowedRoles": [ "authenticated" ]
  }
]

この設定だと、

  • 未認証ユーザーが /.auth/login/ciam にアクセス → 401
  • 401 の結果、responseOverrides でまた /.auth/login/ciam に飛ばす → 401 →…

という具合に「ログインエンドポイントにすら到達できないループ」になります。

必ず以下のように、/.auth/* だけは anonymous にしておきましょう。

"routes": [
  {
    "route": "/.auth/*",
    "allowedRoles": [ "anonymous" ]
  },
  {
    "route": "/*",
    "allowedRoles": [ "authenticated" ]
  }
]

4. ドメインの不一致(特にカスタムドメイン)

認証クッキーはドメインに紐づくため、

  • ログインは https://<your-app>.staticwebapps.net で行っているが、
  • アプリのアクセスは https://example.com(カスタムドメイン)で行っている

というように、ログインとアプリ利用でドメインが分裂していると、クッキーが有効に機能せず、結果的にループになります。

この問題を避けるには、

  • CIAM アプリ登録で
    • https://<your-app>.staticwebapps.net/.auth/login/ciam/callback
    • https://<your-custom-domain>/.auth/login/ciam/callback
    の両方を登録する。
  • 本番では極力カスタムドメイン側で完結させるよう運用を揃える。

5. Issuer / well-known URL の誤り

CIAM の Issuer URL は、微妙な末尾のスラッシュや /v2.0 の有無で間違えやすいポイントです。

  • .../v2.0 が抜けている
  • テナント ID とテナント名(ドメイン)の位置を取り違えている
  • 最後に余計なスラッシュが付いている

といった誤りがあると、SWA 側で「issuer mismatch」と判断され、認証は成功しているように見えてもクッキーが発行されず、ループします。

必ず、メタデータの issuer 値と完全一致する形で設定しましょう。

6. ブラウザのクッキー・セッションの残骸

構成を何度も変えながら試していると、ブラウザ側に古いセッション情報やクッキーが残っていて、動作確認の妨げになることがあります。

  • ブラウザのクッキー/サイトデータを削除する
  • シークレットウィンドウ(InPrivate/プライベートブラウズ)で試す
  • 別ブラウザでも再現するか確認する

といった基本的な切り分けも忘れずに行いましょう。

動作確認の具体的な手順

設定を変えた後は、以下の手順で「どこまでうまく動いているか」を確認します。

1. 直接ログイン URL を叩く

ブラウザで次の URL を開きます。

https://&lt;your-app&gt;.staticwebapps.net/.auth/login/ciam?post_login_redirect_uri=/

ここで CIAM のログイン画面 → MFA → アプリトップ(/)まで戻ってくれば、少なくとも

  • CIAM との連携
  • リダイレクト URI

までは正しく設定されていると考えられます。

2. /.auth/me でクレームを確認する

ログインした状態で、別タブから /.auth/me にアクセスします。

https://&lt;your-app&gt;.staticwebapps.net/.auth/me

もしくは、ブラウザのコンソールで次の JavaScript を実行してもかまいません。

async function getUserInfo() {
  const response = await fetch('/.auth/me');
  const payload = await response.json();
  return payload.clientPrincipal;
}

getUserInfo().then(console.log);

clientPrincipal にメールアドレス等のクレームが入っていれば、クッキーが正しく設定され、SWA が認証状態を認識できていると判断できます。ここで空配列や null になっている場合は、クッキー/ドメイン/issuer のいずれかがおかしい可能性が高いです。

3. 開発者ツールでリダイレクトチェーンを追う

ブラウザの開発者ツール(Network タブ)を開き、ログイン操作を行った際のリクエストの流れを確認します。

  • /.auth/login/ciam から CIAM のエンドポイントへのリダイレクト
  • CIAM から /.auth/login/ciam/callback に戻ってくる 302
  • その後、/ や /.auth/me へのアクセスで 200 が返っているか

などを見て、どこで 401 や 302 が繰り返されているのかを特定します。常に /.auth/login/ciam と / の 302 が繰り返されている場合は、クッキーが受け取れていないか、issuer mismatch などで内部的に破棄されている可能性があります。

4. SWA のログを確認する

Azure Static Web Apps のリソースから、ログストリーミングや Application Insights を使って、

  • issuer mismatch
  • invalid audience
  • クライアントシークレット未設定関連のエラー

などが出ていないか確認しましょう。構成ファイル上でどこを間違えたかのヒントになることが多いです。

よくある質問と実務的な Tips

Q1. スコープ openid profile email を必ず指定する必要はありますか?

いいえ、必須ではありません。これらは OIDC 標準スコープであり、Azure Static Web Apps 側が既定で要求するため、指定しなくても問題なくクレームが取得できます。

一方で、アプリ側で独自にスコープチェックを実装している場合(例:email クレーム必須など)は、明示的に login.scopes に追加しておくとわかりやすくなります。

Q2. Implicit フローやハイブリッドフローの設定は必要ですか?

Azure Static Web Apps のマネージド認証は、基本的に 認可コードフロー(サーバーサイドでコードを受け取り、バックエンドでトークン交換)で動作します。CIAM 側のアプリは 「Web」アプリとして登録し、特別な SPA 設定をしないのがポイントです。

Q3. 本番/ステージング環境ではどう構成すべき?

実務上は、以下のような構成にすると管理しやすくなります。

  • CIAM テナント:1つにまとめる(運用コスト削減のため)。
  • アプリ登録:
    • 本番用 Static Web Apps(カスタムドメイン)
    • ステージング用 Static Web Apps(自動生成ドメイン)
    それぞれに対応したリダイレクト URI を 1 アプリに複数登録。
  • SWA 側:
    • 本番・ステージングともに同じ staticwebapp.config.json を使用しつつ、
    • アプリ設定(AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)だけ環境ごとに切り替える。

こうしておくと、config ファイルを環境ごとに複製せずにすむため、認証まわりのドリフト(差異)が減り、トラブルシューティングもしやすくなります。

まとめ:これで CIAM への移行後の認証ループは解消できる

最後に、本記事で押さえたポイントを整理します。

  • CIAM テナント (ciamlogin.com) は SWA の組み込み azureActiveDirectory ではサポートされないため、customOpenIdConnectProviders として構成する。
  • プロバイダー名(例:ciam)は、ログイン URL / コールバック URL の一部になる。
    • ログイン:/.auth/login/ciam
    • コールバック:/.auth/login/ciam/callback
  • /.auth/* は必ず anonymous で開放し、それ以外を authenticated にする。
  • clientSecretSettingName は、まずは registration 直下にシンプルに書く構成から始め、Key Vault 等を使う場合は公式ドキュメントの clientCredential パターンに慎重に切り替える。
  • カスタムドメインを使う場合は、staticwebapps.net とカスタムドメインの両方のコールバック URIを CIAM 側に登録する。
  • 動作確認では
    • /.auth/login/ciam?post_login_redirect_uri=/
    • /.auth/me
    を活用し、クッキーとクレームが正しく取得できているかを確認する。

これらを一つ一つ確認していけば、MFA 後に延々とサインイン画面へ戻される「認証ループ」は解消され、Microsoft Entra External ID (CIAM) を利用した Azure Static Web Apps の認証を安定して稼働させられるはずです。

この記事を書いた人

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

コメント

コメントする

目次