mcpField
S
setup-auth

setup-auth

github.com/microsoftセキュリティ1.1k241 回インストール今日更新
リポジトリ

setup-auth について

setup-auth は、Power Pages SPA の認証とロールベース認可を設定するスキルです。複数の ID プロバイダーとサイトフレームワークに対応し、認証サービス、認可ユーティリティ、UI、ロール対応パターンを生成します。

主な機能

  1. 01複数の ID プロバイダーに対応
  2. 02ログイン・ログアウトサービスの作成
  3. 03ロール確認ユーティリティと UI の追加
  4. 04ローカルテスト用モックデータ
  5. 05サイトフレームワークに合う実装パターン

活用シーン

  1. 01Power Pages SPA へのサインイン追加
  2. 02Web ロールに応じた UI 表示
  3. 03ローカルでの認証フローのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/microsoft/power-platform-skills --skill setup-auth

インストール後、Claude Code で/setup-authを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
auth-report.html
references/
authentication-reference.md
authorization-reference.md
idp-provisioning-reference.md
scripts/
validate-auth.js

詳細

プラグインの確認:node "${PLUGIN_ROOT}/scripts/check-version.js" を実行します。メッセージが出力された場合は、続行する前にユーザーに表示してください。

認証と認可の設定

Power Pages の SPA サイトに認証(ログイン/ログアウト)とロールベースの認可を設定します。このスキルは、Microsoft Entra ID、Entra External ID(セルフサービス登録に対応した顧客向けアプリ用)、OpenID Connect(Okta、Auth0 など)、SAML2、WS-Federation、ローカル認証(ユーザー名/パスワード)、Microsoft Account、Facebook、Google など、複数の ID プロバイダーに対応しています。また、招待ベースの登録や利用規約への同意などのオプション機能にも対応しています。Power Pages に組み込まれた 2FA は、SendCode/VerifyCode ページがサーバー側でレンダリングされ、SPA のエクスペリエンスに統合できないため、意図的にスキャフォールディングしていません。代わりに IdP レベルの MFA を使用してください。このスキルでは、サイトのフレームワークと選択した ID プロバイダーに適した認証サービス、型宣言、認可ユーティリティ、認証 UI コンポーネント、ロールベースのアクセス制御パターンを作成します。

基本原則

  • クライアント側の認証は UX のためだけです — Power Pages の認証はサーバー側(セッション Cookie)で行われます。クライアント側のロールチェックで制御するのはユーザーに表示される内容であり、アクセスできる内容ではありません。実際のセキュリティはサーバー側のテーブル権限で適用します。
  • フレームワークに適したパターン — フック、コンポーザブル、サービス、ディレクティブ、ガードなど、すべての認証成果物は、検出されたフレームワークの慣用的なパターンと規則に合わせる必要があります。
  • 開発環境との同等性 — Power Pages にデプロイせずに認証フローやロールベースの UI をテストできるよう、ローカル開発用のモックデータを含めます。
  • サイトの整合性を維持する — ナビゲーション、認証ページ、ダイアログ、ロールベースの UI を変更する前に、${PLUGIN_ROOT}/references/site-modification-integrity.md を読んでください。設定済みのロケールリソース間で、表示されるすべての認証状態を同期し、双方向で拡張に安全な状態を保ってください。

初回のリクエスト: $ARGUMENTS

前提条件:

  • /create-site で作成した既存の Power Pages SPA サイト
  • サイトが少なくとも一度デプロイされていること(.powerpages-site フォルダーが存在すること)
  • /create-webroles で Web ロールが作成されていること

ワークフロー

  1. フェーズ 1:前提条件の確認 — サイトの存在、フレームワークの検出、Web ロールの確認
  2. フェーズ 2:計画 — 認証要件を収集します(必要に応じて、IDP アプリ登録をナレッジファースト方式で設定します。デフォルトはガイド付きです。サポートされている場合は、プロバイダー独自の CLI を使って設定を代行できます:Okta/Auth0/Entra External ID)。その後、承認用の計画を提示します。
  3. フェーズ 3:認証サービスの作成 — ログイン/ログアウト機能と型宣言を備えた認証サービスを作成
  4. フェーズ 4:認可ユーティリティの作成 — ロールチェック関数とラッパーコンポーネントを作成
  5. フェーズ 5:認証 UI の作成 — ログイン/ログアウトボタンをナビゲーションに統合
  6. フェーズ 6:ロールベース UI の実装 — サイトコンポーネントにロールベースのパターンを適用
  7. フェーズ 7:認証設定の検証 — すべての認証ファイルの存在、ビルドの成功、認証 UI のレンダリングを検証
  8. フェーズ 8:レビューとデプロイ — 概要とデプロイの確認

フェーズ 1:前提条件の確認

目標: プロジェクトの存在を確認し、フレームワークを特定し、デプロイ状況と Web ロールを検証して、既存の認証コードを確認します。

操作
1.1 プロジェクトの場所を特定する

現在のディレクトリまたは直下のサブディレクトリで powerpages.config.json を探します。

**/powerpages.config.json

見つからない場合:まず /create-site でサイトを作成するよう、ユーザーに伝えてください。

1.2 フレームワークを検出する

package.json を読み取り、フレームワーク(React、Vue、Angular、Astro)を特定します。フレームワークの検出方法の全体については、${PLUGIN_ROOT}/references/framework-conventions.md を参照してください。

1.3 デプロイ状況を確認する

.powerpages-site フォルダーを探します。

**/.powerpages-site

見つからない場合:サイトを先にデプロイする必要があることをユーザーに伝えます。

「.powerpages-site フォルダーが見つかりませんでした。認証を設定する前に、サイトを少なくとも一度デプロイする必要があります。」

🚦 ゲート(plan · setup-auth:1.3.deploy-first): .powerpages-site がありません。このフォルダー内にサイト設定を書き込むため、認証のセットアップには必要です。先にデプロイするか、停止してください。

トリガー: フェーズ 1.3 で .powerpages-site フォルダーが見つかりませんでした。 確認する理由: 自動デプロイでは誤った環境が選ばれます。スキップすると認証の構成が壊れたままになります。 キャンセルした場合: 変更はありません。認証ファイルはまだ書き込まれていません。

AskUserQuestion を使用します。

質問選択肢
サイトを先にデプロイする必要があります。今すぐデプロイしますか?はい、今すぐデプロイする(推奨)、いいえ、後で自分で行います

「はい、今すぐデプロイする」の場合:/deploy-site を実行し、その後処理を再開します。

「いいえ」の場合:停止します。サイトを先にデプロイする必要があります。

1.4 Web ロールを確認する

.powerpages-site/web-roles/ 内の Web ロール YAML ファイルを探します。

**/.powerpages-site/web-roles/*.yml

各ファイルを読み込み、既存の Web ロール(名前、ID、フラグ)の一覧を作成します。

🚦 ゲート(plan · setup-auth:1.4.create-webroles): Web ロールが見つかりません。ロールベースの認可には、少なくとも 1 つのロールが必要です。先にロールを作成するか、スキップして後で追加してください。

トリガー: フェーズ 1.4 で .powerpages-site/web-roles/ 内に YAML ファイルが見つかりませんでした。 確認する理由: /create-webroles を自動実行すると、別の完全なスキルが実行されます。自動的にスキップすると、空のロールセットに対して RBAC チェックが行われます。 キャンセルした場合: 変更はありません。認証ファイルはまだ書き込まれていません。

Web ロールが存在しない場合:認可には Web ロールが必要であることをユーザーに伝えます。AskUserQuestion を使って、先に作成するか確認します。

質問選択肢
Web ロールが見つかりませんでした。ロールベースの認可には Web ロールが必要です。今すぐ作成しますか?はい、先に Web ロールを作成する(推奨)、スキップ — 後でロールを追加します

「はい」の場合:/create-webroles を実行し、その後処理を再開します。

「スキップ」の場合:続行します。認証サービスとログイン/ログアウトは引き続き機能しますが、ロールベースの認可には後でロールを作成する必要があります。

1.5 既存の認証設定を確認する

初回の実行であっても、必ずこの確認手順を実行してください。以前の実行時に、または YAML ファイルを手動で編集した際にサイト設定が追加されている可能性があります。SPA の認証コードがまだ存在しない場合も同様です。サーバー側ですでに設定されているプロバイダーを、気付かないまま削除しないようにすることが目的です。

手順 1 — .powerpages-site/site-settings/ をスキャンし、すでに設定されているプロバイダーを探します。

サイト設定のファイル名を以下のパターンと照合して、既存のプロバイダーを検出します。

パターン対応するプロバイダーの種類
Authentication-OpenIdConnect-{Name}-AuthenticationType.sitesetting.ymlOIDC(Entra External ID、Okta、Auth0、汎用 OIDC、B2C はすべて OIDC 経由で処理されます)
Authentication-SAML2-{Name}-AuthenticationType.sitesetting.ymlSAML2
Authentication-WsFederation-{Name}-AuthenticationType.sitesetting.ymlWS-Federation
Authentication-OpenAuth-{Microsoft|Facebook|Google}-{ClientId|AppId}.sitesetting.ymlソーシャル OAuth
Authentication-Registration-LocalLoginEnabled.sitesetting.yml の値が trueローカル認証

検出した各プロバイダーについて、関連する .sitesetting.yml ファイル一式を読み込み、Authority/MetadataAddress、ClientId/AppId、AuthenticationType(providerIdentifier)、Caption または表示名(存在する場合)、キーで使用されている {Name} のスラッグ(例:OpenIdConnect_1、EntraExternalId)を抽出します。

Entra ID の各バリアントと OIDC を区別する方法 — Authority URL のパターンで判定します。

Authority パターンプロバイダーの種類メモ
https://login.windows.net/{guid}/(/v2.0/ なし)— サイトの親テナントMicrosoft Entra ID(従業員向け) — type: 'entra-id'サイト作成時に Power Pages によって自動入力されます。{Name} スラッグは通常 AzureAD です。AUTH_PROVIDERS では providerIdentifier を undefined に設定してください — 実行時リゾルバーが Portal.tenant から導出します。
https://{subdomain}.ciamlogin.com/{tenantId}(末尾に /v2.0/ なし)Entra External ID — type: 'oidc'顧客テナントです。Authority と一致する明示的な providerIdentifier を指定する必要があります。
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/v2.0/{policy}Azure AD B2C(レガシー)— type: 'oidc'旧バージョンの B2C 製品です。明示的な providerIdentifier を指定する必要があります。
その他の OIDC Authority(Okta、Auth0 など)OIDC — type: 'oidc'明示的な providerIdentifier を指定する必要があります。

Entra ID(従業員向け)のケースは特殊です — Phase 1.5 の検出で、サイト上の Authentication/OpenIdConnect/AzureAD/* 設定(親テナント用に Power Pages が自動作成する設定)が見つかった場合は、EXISTING_PROVIDERS にエントリーを 1 つ追加します。

{
  id: 'entra-id',
  type: 'entra-id',
  displayName: existingCaption || 'Sign in with Microsoft',
  // NO providerIdentifier — resolveProviderIdentifier() derives it from Portal.tenant
}

既存の Authority サイト設定からテナント ID を抽出して、AUTH_PROVIDERS にハードコードし直さないでください。実行時リゾルバーが処理します。これにより、サイトが別のテナントに複製された場合でも SPA コードの移植性が保たれます。

ステップ 2 — 既存の SPA 認証コードを確認します。

以下のファイルがあるか確認し、重要なマーカーを読み取ります。

  • src/services/authService.ts または .js — AUTH_PROVIDERS 配列(現在のパターン)か、単一の AUTH_PROVIDER 定数(レガシー)かを確認します
  • src/types/powerPages.d.ts — 存在するかどうか
  • src/utils/authorization.ts — 存在するかどうか
  • 認証コンポーネント(AuthButton.*、Login.*、Registration.*、RedeemInvitation.* など)— 存在するものを列挙します
  • src/pages/Login.tsx — 現在表示しているプロバイダーを抽出します(AUTH_PROVIDERS のインポート経由か、インラインかを確認します)

ステップ 3 — 確認結果をユーザーに提示します。

サイト設定からプロバイダーが検出された場合は、その構成とともに提示します。

I found these existing auth providers on your site:

  ✓ Entra External ID
    - ProviderName: OpenIdConnect_1
    - Tenant: ba275000-98c8-404d-a6f0-c5450f2aa668
    - ClientId: e728d63e-1190-495a-ae29-663e9cc10877
    - Configured in site settings: yes
    - Surfaced in SPA UI: NO (authService.ts has no entry for this provider)

  ✓ Local Authentication
    - LoginByEmail: true
    - Surfaced in SPA UI: yes

AskUserQuestion を使用します。

質問ヘッダー選択肢
サイトで既存の認証プロバイダーが見つかりました。どうしますか?既存の認証既存のプロバイダーをすべて保持し、新しいプロバイダーを追加する(推奨)— 既存の設定を維持し、次に指定するプロバイダーを追加します、既存のプロバイダーをすべて保持する(今回、新しいプロバイダーは追加しない)— SPA コードを再生成して、サイト設定にすでにあるものを表示します、すべてを新しい構成に置き換える — 既存のサイト設定と SPA コードを消去して、最初からやり直します

「既存のプロバイダーをすべて保持し、新しいプロバイダーを追加する」(デフォルトのパス):

  • 検出したプロバイダーを EXISTING_PROVIDERS として保存します。これらは Phase 3.2 で生成する AUTH_PROVIDERS 配列にマージされます
  • Phase 2.1 では、追加する新しいプロバイダーについて質問します。既存のプロバイダーには手を加えません
  • ローカル認証の場合 — Local Authentication が EXISTING_PROVIDERS に含まれている場合は、必ずローカル認証の SPA コード(ログインフロー、登録ページ、パスワードの再設定、招待の引き換え)をユーザーの Phase 2.1 の回答に基づいて再生成します。手動編集されたローカル認証コードを保持しようとしないでください。ローカルフローは複雑なため、一部だけを更新すると、回避できる不具合よりも多くの不具合が発生します。

「既存のプロバイダーをすべて保持する(今回、新しいプロバイダーは追加しない)」:

  • Phase 2.1 のプロバイダー選択の質問を完全にスキップします
  • EXISTING_PROVIDERS のみから AUTH_PROVIDERS を再導出します
  • 次のような場合に便利です。サイト設定にはすでにプロバイダーがあるのに、SPA UI に表示されていないサイトを修正する場合(この分岐は、まさにこの不具合を修正するために追加されました)

「すべてを新しい構成に置き換える」:

  • EXISTING_PROVIDERS = [] に設定します
  • Phase 8.1 の一環として、既存の OIDC/SAML2/WsFed/OpenAuth サイト設定 YAML を削除します
  • プロバイダーが存在しない場合と同様に Phase 2.1 を実行します

「スキップ/変更なし」の選択肢は提示しないでください。 ユーザーが setup-auth を実行した場合、認証を設定する意図があります。何もせずに黙って終了するより、確認する方が適切です。

出力
  • プロジェクトのルートパスを確認済み
  • フレームワークを特定済み(React、Vue、Angular、または Astro)
  • デプロイ状況を確認済み
  • Web ロールの一覧を作成済み
  • サイト設定から作成した EXISTING_PROVIDERS リスト。各プロバイダーの種類、ProviderName スラッグ、ClientId/Authority などを含む
  • MERGE_MODE を選択済み:keep-and-add(デフォルト)| keep-only | replace-all
  • SPA 認証ファイルの一覧を記録済み(存在するファイル、AUTH_PROVIDERS 配列を使用しているか、従来の単一プロバイダーパターンを使用しているか)

Phase 2:計画

目標: ユーザーから認証要件を収集し、承認を得るために実装計画を提示します。

アクション
2.0 スマートな認証設定の推定(質問する前)

ユーザーに利用したいプロバイダーを尋ねる前に、Phase 1 のサイトコンテキスト(サイト名、目的、対象者の種類)を分析し、適切な認証設定を自動的に推定します。

推定ルール:

サイトの種類推定される認証設定理由
社内/従業員ポータル(人事、ダッシュボード、管理者向け)Entra ID + 招待制登録(OpenRegistrationEnabled=false、InvitationEnabled=true)社内サイトでは、招待された従業員のみにアクセスを制限する必要があります
顧客向けポータル(サポート、セルフサービス)Entra External ID + オープン登録顧客向けポータルでは、顧客がセルフサービスで登録できる必要があります
パートナーポータル(B2B、ベンダー)Entra ID + 招待制登録パートナーは事前審査済みのため、オープン登録はセキュリティ上のリスクです
保護機能を備えた公開サイト(EC、コミュニティ)Entra External ID + オープン登録 + オプションの Google/Facebook公開サイトでは、手間のかからない登録を実現するソーシャルログインが有効です
ローン/金融/銀行ポータルEntra External ID + 招待制登録金融サイトでは、コンプライアンスのためにアクセスを制御する必要があります

確信を持って推定できる場合は、理由とともに推奨設定を提示します。

「サイトの目的({purpose})に基づき、次の設定をおすすめします。

  • 認証には {provider}
  • {registration mode}。理由は {rationale} です。

この設定で進めますか、それとも別のプロバイダーを選びますか?」

質問選択肢
この推奨設定で進めますか?はい、推奨設定で進める、いいえ、プロバイダーを選ぶ

「はい」の場合:Phase 2.1 のプロバイダー選択をスキップし、推奨プロバイダーの詳細(ClientId、テナント名など)の収集に直接進みます。

「いいえ」の場合、または確信を持って推定できない場合:以下の Phase 2.1 に進みます。

2.1 要件の収集

🚦 ゲート(plan · setup-auth:2.1.requirements): 実装する認証機能(ログイン+ログアウト/RBAC/両方)を選択します。「どのロールにアクセス権を付与しますか」という条件付きの追加質問も同じステップで行います。

トリガー: Phase 2.1 の開始時。 質問する理由: 機能セットを誤ると、ユーザーがログインだけを希望しているのに RBAC ファイルを作成するなどの問題が発生します。 キャンセル時の状態: なし。認証ファイルはまだ書き込まれていません。

再実行時の処理 — Phase 1.5 で既存のプロバイダーが検出された場合:

動作は Phase 1.5 で選択した MERGE_MODE によって異なります。

  • keep-only(ユーザーが「既存の設定をすべて維持し、今回は新しいプロバイダーを追加しない」を選択)→ 新しいプロバイダーの選択に関する質問は省略します。ローカル認証が検出された場合に限り、「ローカル認証」の追加質問に進みます。フェーズ 3.2 では、EXISTING_PROVIDERS のみから AUTH_PROVIDERS を生成します。
  • keep-and-add(デフォルト — ユーザーはもう 1 つ追加したい)→ 何を追加するかユーザーに尋ねます。以下のプロバイダー選択の質問は引き続き複数選択にしてください(ユーザーは一度に複数の新しいプロバイダーを追加する可能性があります)。ただし、既存のプロバイダーはリストに含めません(すでに設定済みであり、この質問では何を新しく追加するかを尋ねています)。よくあるパターン:
    • Entra External ID を利用中で、ローカル認証を追加したい場合 → ユーザーが「ローカル認証」を選択 → ローカル認証に関する追加質問を行う → フェーズ 3.2 で統合
    • Entra External ID とローカル認証を利用中で、2 つ目の Entra External ID テナントを追加したい場合 → ユーザーが「Entra External ID」を選択 → Authority/ClientId の収集後、次のように尋ねます:"すでにテナント {existing-tenant} 用の Entra External ID プロバイダーが設定されています。この新しいプロバイダーは別のインスタンスです。異なる ProviderName スラッグを指定してください(サイト設定キー Authentication/OpenIdConnect/{ProviderName}/* で使用され、コードではプロバイダー ID として使用されます)。" ユーザーにスラッグを選んでもらいます(次の連番をデフォルトとし、例:OpenIdConnect_2)。または、カスタム名(例:EntraExternalId_Employee)を選んでもらいます。
  • replace-all(ユーザーがすべて消去することを選択)→ 初回実行時と同様に、プロバイダー選択の質問を行います。

「複数のインスタンスを設定しますか?」と先回りして尋ねてはいけません。 まずは一度に 1 つのプロバイダーを設定するようユーザーを案内します。1 つの設定が完了し、もう 1 つ追加したい場合は、ユーザーが setup-auth を再実行できます → フェーズ 1.5 で既存の設定を検出 → keep-and-add モードのフェーズ 2.1 で「次に何を追加しますか?」と尋ねます。これにより、よくあるケース(プロバイダーを 1 つ設定する場合)の質問数を抑えながら、高度なケース(複数のテナント)にも対応できます。

重要:複数のプロバイダーに対応しています。 ユーザーは複数の ID プロバイダー(例:Entra External ID + Google)を利用したい場合があります。ユーザーの最初のプロンプトに具体的なプロバイダーが記載されている場合は、プロバイダー選択の質問を省略し、記載された各プロバイダーの詳細収集に直接進みます。

重要 — ローカル認証: ローカル認証をデフォルトで設定してはいけません。プロバイダー選択リストに含めず、推測による提案でも推奨せず、ユーザーが明示的かつ具体的に希望した場合(例:「ユーザー名とパスワードでログインしたい」「ローカルログインを設定して」「ローカル認証を追加して」)に限り設定してください。外部 ID プロバイダー(Entra External ID、Entra ID、OIDC など)を常に優先します。「ログインを追加して」のような曖昧な指示の場合は、外部プロバイダーをデフォルトとして選び、ローカル認証を選んではいけません。

ユーザーが利用するプロバイダーを指定していない場合は、AskUserQuestion を使って ID プロバイダーを確認します。これは複数選択式の質問です。ユーザーは 1 つ以上選択できます。

質問選択肢
利用する ID プロバイダーを選択してください(該当するものをすべて選択)Entra External ID(推奨) — セルフサービス登録が可能な顧客 ID(CIAM)、Microsoft Entra ID — 社内/従業員向けサイト用の Azure AD/Entra ID、OpenID Connect — Okta、Auth0、または OIDC 準拠の任意のプロバイダー、SAML2 — SAML 2.0 ID プロバイダー(ADFS、Shibboleth、Login.gov など)、WS-Federation — WS-Federation ID プロバイダー、Microsoft Account — Microsoft の個人用/職場用アカウントでサインイン、Facebook — Facebook でサインイン、Google — Google でサインイン

次に、選択したプロバイダーごとに、以下の必須の追加質問を行います。 プロバイダーを省略してはいけません。次に進む前に、選択したすべてのプロバイダーについて設定情報を収集する必要があります。

各プロバイダーについて、値の確認先がわかるよう、関連する Microsoft Learn のドキュメントリンクも共有します。

「Microsoft Account」の場合:

質問選択肢
Microsoft アプリ登録のクライアント ID は何ですか?(例:a1b2c3d4-e5f6-7890-abcd-ef1234567890)(自由入力)

ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings

「Facebook」の場合:

質問選択肢
Facebook Developer Console のアプリ ID は何ですか?(例:1234567890123456)(自由入力)

ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/facebook-settings

「Google」の場合:

質問選択肢
Google Cloud Console のクライアント ID は何ですか?(例:123456789-abc.apps.googleusercontent.com)(自由入力)

ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/oauth2-google

「OpenID Connect」の場合(Okta、Auth0 など):

まず、使用する OIDC プロバイダーを特定します。設定手順と参照するドキュメントは、ベンダーによって異なります。

質問選択肢
どの OpenID Connect プロバイダーを使用していますか?Okta、Auth0、その他(OIDC 準拠プロバイダー)

以下の質問とフェーズ 2.1.2 のステップ A(プロバイダーのドキュメントを読む)では、回答を {provider} として使用します。「その他」の場合は、プロバイダー名をユーザーから聞き取ってください。

入力を求める前に、まずユーザーに説明してください。 {provider} を選択したら、{provider} でこのサイトのアプリ登録を設定することを伝えます。最初に行うことは、{provider} 独自のドキュメントを読むことです。設定方法はプロバイダーによって異なります。その後、{provider} がサポートする内容に応じて、{provider} のコンソールでユーザー自身が設定できるよう案内するか、{provider} が対応していれば、ユーザーに代わって設定します。どちらの場合も、メタデータ、リダイレクト URI、クレームは自動的に導き出されます。また、アプリではシークレットを管理する必要のない code id_token フローを使用します。

{provider} のテナント(サインインドメイン。Okta の場合は your-tenant.okta.com、Auth0 の場合は your-tenant.us.auth0.com)を聞いてください。

質問選択肢
{provider} のテナントは何ですか?(自由入力)
ログインボタンに表示する名前は何にしますか?(既定値:Sign in)(自由入力、既定値あり)
{provider} に、このサイト用のアプリ登録はすでにありますか?いいえ — フェーズ 2.1.2 で設定します(推奨)、はい — すでにあります

ユーザーが**「はい — すでにあります」と回答した場合は、既存のクライアント IDとメタデータアドレスを今すぐ聞いてください(フェーズ 8.1 で必要です)。フェーズ 2.1.2 のアプリ登録設定はスキップします。それ以外の場合、クライアント ID は事前に聞きません。クライアント ID はフェーズ 2.1.2**でアプリを設定する際(手順を案内するか、プロバイダーが対応していればユーザーに代わって設定)に取得し、プロバイダーのディスカバリードキュメントからメタデータを導き出します。

ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings

「Entra External ID」の場合 — 以下の 4 段階の手順に沿って進めてください。Authority、ClientId、Metadata を最初にユーザーに尋ねるだけにしないでください。これらの値は、テナント、アプリ登録、ユーザーフローに由来しますが、ユーザーがまだ設定していない可能性があります。対応する値を尋ねる前に、各前提条件を順に確認してください。

参考ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/entra-external-id 以下の手順で参照する Entra External ID の前提条件については、${PLUGIN_ROOT}/skills/setup-auth/references/authentication-reference.md もご覧ください。 このサイト用に事前計算する値 — 手順を開始する前に、以下を計算します。

  • SITE_URL = デプロイ済みサイトの URL(例:https://site-597pv.powerappsportals.com)。pac env who とサイト名から読み取るか、サイトの既存の設定から取得します。
  • PROVIDER_NAME = 新規追加の場合、デフォルトは OpenIdConnect_1 です(または、フェーズ 8.1 の CallbackPath 一意性ロジックに従って、次に空いている OpenIdConnect_N のスラッグ)。複数インスタンス構成の場合、ユーザーは EntraExternalId_Customer のようなカスタムスラッグに変更できます。
  • REDIRECT_URI = {SITE_URL}/signin-{PROVIDER_NAME-lowercased} — 例:https://site-597pv.powerappsportals.com/signin-openidconnect_1。この値をそのまま Entra アプリ登録に貼り付けます。
  • APP_NAME_SUGGESTION = power-pages-{site-shortname} — 例:power-pages-savoria
  • USER_FLOW_NAME_SUGGESTION = {site-shortname}-signupsignin — 例:savoria-signupsignin

手順 1 の前にこれらをユーザーに表示し、すぐ参照できるようにします。

手順 1 — テナント
質問ヘッダー選択肢
Microsoft Entra External ID テナントはすでにありますか?(これは通常の組織向け Entra ID テナントとは別の種類のテナントです。CIAM と呼ばれることもあります。)テナントはい — External ID テナントがあります、いいえ — 作成をサポートしてください(30 日間の無料試用版)、よくわかりません

「いいえ」の場合、以下を表示します。

Entra External ID テナントを作成する手順:

  1. https://entra.microsoft.com/ を開きます
  2. テナントの所有者となるアカウントでサインインします
  3. 上部から テナントの管理 → 作成 をクリックします
  4. External(顧客向け) を選択します。Workforce ではありません。
  5. ドメインのプレフィックス(テナントのサブドメイン)を選びます。例:contoso は contoso.ciamlogin.com になります。これはすべてのログイン URL に表示されます。
  6. 30 日間の無料試用版では、クレジットカードは不要です。後から有料の Azure サブスクリプションを関連付けることができます。

詳細なガイド:https://learn.microsoft.com/en-us/entra/external-id/customers/quickstart-tenant-setup

テナントを作成したら、そのテナントに切り替えて(entra.microsoft.com の右上にあるテナント選択ツールを使用)、ここに戻ってください。

「よくわかりません」の場合、次のメッセージを表示します。「https://entra.microsoft.com/ → 右上のテナント選択ツールを確認してください。顧客向けテナントには External と表示されています。Workforce テナントは別の製品のため、使用できません。」

次に、テナント識別子を収集します。

質問選択肢
テナントのサブドメインは何ですか?(.ciamlogin.com より前の部分です。例:contoso。External ID テナントの概要ページの「プライマリ ドメイン」で確認し、.onmicrosoft.com を削除してください。)(自由記述)
テナントの ID(GUID)は何ですか?(External ID テナントの概要ページの「テナント ID」で確認できます。a1b2c3d4-e5f6-7890-abcd-ef1234567890 のような形式です。)(自由記述)

検証:サブドメインが ^[a-z0-9-]+$ に一致すること(ドット、大文字、.ciamlogin.com のサフィックスがないこと)、テナント ID が UUID の正規表現に一致することを確認します。いずれかが一致しない場合は、正しい形式を示して再入力を求めます。

EXTERNAL_ID_TENANT_SUBDOMAIN および EXTERNAL_ID_TENANT_ID として保存します。

手順 2 — アプリ登録

まずリダイレクト URI を確認します。 このスキルではサイト URL と PROVIDER_NAME に基づくデフォルト値を事前計算しますが、ユーザーは別の URI を希望する場合があります。

Power Pages サイトでは、アプリ登録にリダイレクト URI を登録する必要があります。サイト URL とプロバイダー名に基づくデフォルト値は次のとおりです。

{REDIRECT_URI}

このデフォルト値を使用するか、別の URI を指定できます。例:{SITE_URL}/signin-entra-customer または {SITE_URL}/auth/external-id。ホストは Power Pages サイトである必要があります。変更できるのはパスのみです。

質問ヘッダー選択肢
このリダイレクト URI を使用しますか?リダイレクト URIデフォルトを使用(推奨) — {REDIRECT_URI}、別の URI を使用

「別の URI を使用」の場合、次を尋ねます。

質問選択肢
リダイレクト URI を入力してください({SITE_URL} 上にあり、{SITE_URL}/ で始まり、スペースやクエリ文字列を含まないこと)。例:{SITE_URL}/signin-entra-customer。(自由記述)
カスタム URI を検証します。
  • {SITE_URL}/ で始まっている必要があります
  • パス部分が ^/[a-zA-Z0-9_\-/]+$ に一致する必要があります(英数字、ハイフン、アンダースコアを使用でき、追加のスラッシュも許可されます)
  • パスが、.powerpages-site/site-settings/ にすでに存在する Authentication/OpenIdConnect/*/CallbackPath(フェーズ 1.5 の調査結果)と重複してはなりません
  • パスが予約済みの Power Pages サーバーパス(/Account/...、/SignIn、/Register、/_layout/...、/api/...)であってはなりません

入力が無効な場合は、再度入力を求めます。その後、この値を REDIRECT_URI として保存し、以降の手順とフェーズ 8.1 で使用します。

注:このスキルは、単一の REDIRECT_URI から 2 つのサイト設定を作成します。ユーザー向けの RedirectUri(完全な URI。IdP に送信されます)と、内部用の CallbackPath(パス部分のみ。受信リクエストの処理対象を OWIN ミドルウェアが判別するために使用されます)です。作成者が CallbackPath について個別に考慮する必要はありません。このスキルが REDIRECT_URI からパス部分を抽出して自動的に導出します。

質問ヘッダー選択肢
この Power Pages サイト用のアプリを Entra External ID テナントに登録しましたか?アプリ登録いいえ — 手順を教えてください(初めての場合に推奨)、はい — Application (client) ID を持っています

「いいえ」の場合は、確認済みの Redirect URI をそのまま含めて、手順を順番に表示します。

アプリを登録する手順:

  1. https://entra.microsoft.com/ にアクセスし、External ID テナントにいることを確認します(右上のピッカーを使用)

  2. アプリケーション → アプリの登録 → 新規登録

  3. 名前:{APP_NAME_SUGGESTION}(または任意の名前)

  4. サポートされているアカウントの種類:**この組織ディレクトリのアカウントのみ(シングル テナント)**を選択します。Power Pages ではこの設定を推奨します。マルチテナント構成では、セキュリティ上、メールアドレスによるコンタクトのマッピングが強制的に無効になります。

  5. リダイレクト URI:Webを選択し、次を正確に貼り付けます。

    {REDIRECT_URI}
    

    (この値をそのままコピーしてください。この値と RedirectUri サイト設定が一致しない場合、サインインに失敗し、AADSTS50011: The reply URL specified in the request does not match が表示されます。)

  6. 登録をクリックします

  7. 認証タブを開き、「暗黙的な許可およびハイブリッド フロー」でアクセス トークンとID トークンの両方をオンにして、保存をクリックします

  8. API のアクセス許可タブを開き、{your tenant} に管理者の同意を与えますをクリックして、確認します

  9. 概要タブに戻り、Application (client) ID(GUID)をコピーします

詳細ガイド:https://learn.microsoft.com/en-us/entra/external-id/customers/quickstart-register-app

「はい」(既存のアプリ)の場合は、Client ID を尋ねる前に、対応する Redirect URI が登録されていることも確認します。

続行する前に、既存のアプリ登録に次の Redirect URI が登録されていることを確認してください(Entra 管理センターの 認証 → Web にあります)。

{REDIRECT_URI}

登録されていない場合、または値が異なる場合は、今すぐ追加してください。アプリ登録には複数の Web Redirect URI を登録できるため、こちらの URI を追加しても既存の連携には影響しません。Power Pages の値が登録済み URI と完全に一致しない場合、サインインに失敗します。

続いて、値を尋ねます。

質問選択肢
概要タブから Application (client) ID を貼り付けてください。(自由入力)

検証:UUID v4 形式(^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)に一致する必要があります。一致しない場合は、再度入力を求めます。

EXTERNAL_ID_CLIENT_ID として保存します。

クライアント シークレットについては尋ねないでください。 Entra External ID のアプリ登録は PKCE を使用するパブリック クライアントであるため、シークレットは不要です。このスキルは ClientSecret なしでサイト設定を作成し、このプロバイダーではフェーズ 8.1.1(Key Vault)をスキップします。シークレットが必要なコンフィデンシャル クライアントのシナリオの場合は、デプロイ後に Power Pages 管理センターから手動で追加できます。この方法は、フェーズ 8.5 のデプロイ後の注意事項に上級者向けのオーバーライドとして記載してください。

ステップ 3 — ユーザーフロー

ユーザーフローでは、ユーザーから収集する属性と、ID トークンに含めるクレームを定義します。ユーザーフローがない場合、IdP からリダイレクトされた後にサインインが失敗します。

質問ヘッダー選択肢
Entra External ID テナントでサインアップ/サインインのユーザーフローを作成し、アプリに関連付けましたか?ユーザーフローいいえ — 作成手順を案内してください(初めての場合におすすめ)、はい — ユーザーフロー名が分かります

「いいえ」の場合、手順で選択するユーザーフローの属性は、後で収集する PROFILE_MAPPING_CHOICE(トラック B のプロファイルマッピングに関する質問)と一致させる必要があります。このステップはその質問よりも先に実行されるため、ここで質問してください(Entra External ID の場合のみ)。

ユーザーフローでは、ユーザーから収集する属性と、トークンで返すクレームを指定する必要があります。このスキルはクレーム → Dataverse の連絡先フィールドへのマッピングを自動で行います。ここで選択した属性によって、利用できる情報が決まります。

質問ヘッダー選択肢
サインアップフォームで収集し、クレームとして返すユーザープロファイル情報は何ですか?プロファイル属性標準(おすすめ)— メール、名、姓、標準+電話 — 電話番号も含む、メールのみ — 最小限のサインアップフォーム

PROFILE_ATTRIBUTES_CHOICE として保存します(これによりトラック B の PROFILE_MAPPING_CHOICE も決まります。両者は一致させてください。ユーザーが明示的に異なる選択をしない限り、両方のデフォルトを「標準」にします)。

次に、以下を表示します。

ユーザーフローの作成手順:

  1. https://entra.microsoft.com/ にアクセスし、External ID テナントを開きます
  2. External Identities → User flows → New user flow
  3. Name:{USER_FLOW_NAME_SUGGESTION}(または任意の名前 — 使用できるのは英字、数字、ハイフン、アンダースコアのみ)
  4. サインイン用の Identity providers:Email with password(おすすめ — 多くのお客様にとって最もなじみやすい方法)または Email one-time passcode(パスワードレス)を選択します
  5. 収集する User attributes(サインアップフォームのフィールド):上記の選択に基づいて、以下を選択します。
    • Standard / Standard + phone:☑ Email Address、☑ Given Name、☑ Surname{, ☑ Phone Number(Standard + phone の場合)}
    • Email only:☑ Email Address
  6. クレームとして返す User attributes(ID トークン内):上記と同じ項目を選択します。これらの属性を使ってプロファイルを Dataverse の連絡先フィールドにマッピングします
  7. Create をクリックします
  8. 作成したユーザーフローを開き、Applications タブ → Add application → ステップ 2 で登録したアプリを選択 → Select

詳細ガイド:https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-user-flow-sign-up-sign-in-customers

次に、以下を質問します。

質問選択肢
作成した ユーザーフロー名を入力してください(例:{USER_FLOW_NAME_SUGGESTION})。(自由入力)

検証:^[a-zA-Z0-9_-]+$ に一致する必要があります(英字、数字、ハイフン、アンダースコア)。一致しない場合は、再入力を求めます。

EXTERNAL_ID_USER_FLOW として保存します。

ステップ 4 — 表示名+確認
質問選択肢
ログインボタンのラベルは何にしますか?デフォルト:Sign in with Entra External ID(横並びのログインページレイアウトで1行に収まるよう、「Sign in with Microsoft Entra External ID」から短縮しています — 下記の注を参照)。「Sign in with Microsoft」は使用しないでください。Microsoft Account のソーシャルプロバイダーと競合します。(自由入力、デフォルト値あり)

表示名の長さの目安:横並びレイアウト(デフォルト)のログインページで1行に表示できるよう、ラベルは28文字以下を目安にしてください。より長いラベルも使用できます。ボタンが縦に広がり、テキストが2行に折り返されますが、1行に収まるボタンのほうが洗練された印象になります。参考例:

  • 「Entra External ID でサインイン」— 30文字(幅の狭いカードでは折り返され、幅の広いカードでは収まります)
  • 「Microsoft Entra External ID でサインイン」— 40文字(デフォルトの横並びレイアウトでは2行に折り返されます)
  • 「顧客サインイン」— 16文字(常に1行に収まりますが、説明が簡潔です)

ユーザーが複数の外部プロバイダー(例:Entra External ID と Google)を設定している場合、各ボタンに使える幅が狭くなるため、短いラベルがより重要になります。プロバイダーが1つのサイトでは、ボタンが行全体に広がるため、長いラベルでも問題ありません。

EXTERNAL_ID_DISPLAY_NAME として保存します。

次に、設定を導き出して、確認用の概要を提示します。

  • Authority:https://{EXTERNAL_ID_TENANT_SUBDOMAIN}.ciamlogin.com/{EXTERNAL_ID_TENANT_ID}(末尾に /v2.0/ を付けません。Entra External ID では B2C 形式の URL ではなく、テナントパスをそのまま使用します)
  • MetadataAddress:https://{EXTERNAL_ID_TENANT_SUBDOMAIN}.ciamlogin.com/{EXTERNAL_ID_TENANT_ID}/v2.0/.well-known/openid-configuration
  • AuthenticationType(AUTH_PROVIDERS 配列および ExternalLogin POST で使用するプロバイダー識別子):Authority と同じ値
  • RedirectUri:{REDIRECT_URI}(先ほど算出した値)
  • ClientId:{EXTERNAL_ID_CLIENT_ID}

以下の概要をインラインで提示します。

次の内容で設定します:

フィールド値
プロバイダーMicrosoft Entra External ID
テナント{subdomain}.ciamlogin.com({tenantId})
アプリ(クライアント)ID{clientId}
ユーザーフロー{userFlowName}
リダイレクト URI{REDIRECT_URI}(アプリに登録済みである必要があります)
Authority{authority}(導出値)
メタデータ{metadataAddress}(導出値)
表示名{displayName}
ログインボタン"{displayName}"
クライアントシークレットなし(パブリッククライアント/PKCE)

このままサイト設定を書き込みますか?

質問選択肢
続行しますか?はい — サイト設定を書き込む、いいえ — 値を調整する

「いいえ」の場合は、変更したい値を指定するよう再度尋ねます。

実装に関する注意:Power Pages サーバーは Entra External ID を汎用の OpenID Connect プロバイダーとして扱い、CIAM 固有の処理は行いません。すべての設定は Authentication/OpenIdConnect/{ProviderName}/ の下に置きます。/Account/Login/ExternalLogin に POST する provider 値は、AuthenticationType サイト設定と一致する必要があります。デフォルトでは、Authority URL と同じ値です。

「SAML2」の場合:

質問選択肢
SAML2 ID プロバイダーのメタデータエンドポイント URL は何ですか?(例:https://adfs.contoso.com/FederationMetadata/2007-06/FederationMetadata.xml)(自由入力)
ログインボタンに表示する名前は何ですか?(例:Sign in with ADFS、Sign in with Login.gov)(自由入力)

ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/saml2-settings Login.gov(米国政府の SAML IdP)— メタデータエンドポイントは SAML 開発者ガイドで確認してください:https://developers.login.gov/saml/getting-started/

「WS-Federation」の場合:

質問選択肢
WS-Federation プロバイダーのメタデータエンドポイント URL は何ですか?(例:https://adfs.contoso.com/federationmetadata/2007-06/federationmetadata.xml)(自由入力)
プロバイダーのレルムまたは識別子は何ですか?(例:https://adfs.contoso.com/adfs/services/trust)(自由入力)
ログインボタンに表示する名前は何ですか?(例:Sign in with ADFS)(自由入力)

ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/ws-federation-settings

プロフィールのマッピング(すべての外部プロバイダーが対象 — OIDC、Entra External ID、SAML2、WS-Federation、ソーシャル) プロバイダーの基本情報を収集したら、IdP から Dataverse の連絡先にどのユーザープロフィール情報を渡すかを尋ねます。この手順を省略しないでください。省略すると、連絡先レコードの firstname/lastname が空になり、SPA ではどこでもメールアドレスまたはユーザー名が表示されるようになります。

質問ヘッダー選択肢
ID プロバイダーから Dataverse の連絡先レコードにコピーするプロフィール情報を選択してください。プロフィールのマッピング標準(推奨) — 初回サインイン時に名、姓、メールアドレスをコピーする、標準+電話番号 — 携帯電話番号もコピーする、カスタム — マッピングする連絡先フィールドとクレームを選択する、なし — 連絡先フィールドを空のままにする(サーバーはメールクレームから emailaddress1 を引き続き設定します)

PROFILE_MAPPING_CHOICE として保存します。次に、以下を尋ねます。

質問ヘッダー選択肢
プロフィール情報はログインのたびに更新しますか、それとも初回サインイン時のみ更新しますか?同期頻度初回サインイン時のみ(推奨) — 連絡先の作成時にクレームを一度だけコピーし、その後はユーザーが自分のプロフィールを編集しても上書きされないようにする、両方 — 初回サインイン時とログインのたびに同期する(IdP が唯一の信頼できる情報源であり、Power Pages でユーザーにプロフィールを編集させない場合のみ使用してください)

PROFILE_SYNC_FREQUENCY として保存します。これにより、RegistrationClaimsMapping(初回サインイン時のみ)に加えて LoginClaimsMapping(ログインのたびに)を書き込むかどうかが決まります。

「初回サインイン時のみ」がデフォルトになった理由:このスキルでは、サインイン済みユーザーが自分の連絡先情報を編集できる SPA プロフィールページ(/user-profile)を任意で生成します。LoginClaimsMapping が設定されていると、次回ログイン時にサーバーがユーザーの編集内容を IdP のクレームで上書きしてしまいます。これは混乱を招き、ユーザーの作業を気づかれないまま取り消すことになります。「初回サインイン時のみ」なら、連絡先の作成後はユーザー自身がプロフィールを管理できます。IdP がこれらのフィールドに関する唯一の信頼できる情報源(例:人事部門が管理する従業員ディレクトリ)であり、エンドユーザーによる編集を保持すべきでない場合に限り、「両方」に切り替えてください。

クレームタイプの値 — マッピング形式は、カンマ区切りの contactfield=claimtype です(JSON ではありません)。Entra External ID などの OIDC プロバイダーでは、OIDC の短縮名を使用します。

選択肢生成されるマッピング
標準firstname=given_name,lastname=family_name,emailaddress1=email
標準+電話番号firstname=given_name,lastname=family_name,emailaddress1=email,mobilephone=phone_number
カスタムループして、ユーザーが完了と言うまで contactfield=claimtype のペアを一つずつ尋ねます。OIDC の短縮名(given_name、family_name、email、phone_number、preferred_username、カスタムクレーム名)を提案します。contactfield が既知の Dataverse 連絡先列であることを確認します。
なしRegistrationClaimsMapping または LoginClaimsMapping の設定を書き込みません。

SAML2/WS-Federation では、クレームタイプは URI です(例:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname)。それに応じて「標準」で生成されるマッピングを調整します。ソーシャルプロバイダーでは、クレームタイプはプロバイダーごとに異なります(Google:given_name、Facebook:name)。

連絡先のリンク(すべての外部プロバイダーが対象)

メールアドレスを基準に、外部サインインを既存の連絡先に自動リンクするかどうかを尋ねます。

質問ヘッダー選択肢
ユーザーが外部プロバイダーでサインインし、そのメールアドレスが既存の Dataverse 連絡先と一致した場合、どうしますか?連絡先のリンク既存の連絡先にリンク(推奨) — メールアドレスの一致に基づいて自動リンクし、管理者が事前にレコードを作成していても連絡先が重複しないようにする(シングルテナントのプロバイダーのみ — 下記の警告を参照)、新しい連絡先を作成 — 常に新しい連絡先を作成し、自動リンクしない(IdP がメールアドレスを検証しない場合に、より安全な選択肢)

CONTACT_LINKING_CHOICE として保存します。これは AllowContactMappingWithEmail を設定します(「リンク」の場合は true、「新しい連絡先を作成」の場合は false)。

「既存の連絡先にリンク」をデフォルトにする理由:一般的なフローでは、管理者が Dataverse に連絡先レコードを事前に作成し(招待やインポートによることが多い)、ユーザーが設定済みの IdP を介して初めてサインインしたときに、まさにその連絡先が取得されることを期待します。リンクしない場合、サーバーは新しい連絡先を作成し、事前に作成されたレコードは孤立したままになるため、ユーザーを混乱させ、誤診も招きやすくなります。検証済みのメールアドレスによるリンクは、IdP の ID を既存の CRM レコードに紐付ける、広く知られたパターンです。

⚠ 自動リンクは移行/プロビジョニング済みの連絡先向けです:Dataverse に連絡先がすでに存在し(データ移行、管理者によるインポート、招待)、メールアドレスで照合する必要がある場合は、リンクを有効にしてください。既存の連絡先がない新規サイトでは、通常の初回サインイン時の連絡先作成はこれまでどおり機能します。照合対象がないため、リンクは何もしません。

⚠ マルチテナントの安全性:マルチテナントの Entra External ID(Authority が /organizations/ または /common/ を使用する場合、あるいは IssuerFilter がワイルドカードの場合)、Power Pages サーバーはサイト設定にかかわらず、AllowContactMappingWithEmail を強制的に無効化します(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。理由:メールアドレスのクレームはテナント間で信頼できないためです。ユーザーが「既存の連絡先にリンク」を選択しても、Authority がマルチテナントの場合はリンクが機能しないことを警告し、シングルテナントの Authority を推奨してください。

⚠ セキュリティ:AllowContactMappingWithEmail = true の場合、被害者のメールアドレスを使って設定済み IdP にサインインできる攻撃者が、被害者の連絡先を乗っ取るおそれがあります。IdP がメールアドレスを検証する場合に限り有効にしてください(シングルテナントの Entra External ID では検証されますが、任意の OIDC では検証されない場合があります)。メールアドレスの検証方針を管理できない IdP(汎用の OIDC エンドポイントなど)を設定する場合は、「新しい連絡先を作成」に切り替えてください。

「ローカル認証」の場合(ユーザーが明示的に希望した場合のみ):ログイン時にユーザーをどのように識別するかを尋ねます。

質問選択肢
ユーザーはローカルアカウントでどのようにログインしますか?メールアドレスでログイン(推奨) — ユーザーはメールアドレスでサインインします、ユーザー名でログイン — ユーザーは自分で選んだユーザー名でサインインします

この選択によって Authentication/Registration/LocalLoginByEmail サイト設定(メールアドレスの場合は true、ユーザー名の場合は false)が決まり、ログイン、登録、認証サービスのコードにあるすべてのフォームフィールドに影響します。メールアドレスを選択すると、ログインフォームと登録フォームに Email フィールド(タイプ email)が表示されます。ユーザー名を選択すると、フォームに Username フィールド(タイプ text)が表示され、登録フォームでは Email が別の必須フィールドになります(連絡先レコードのためにサーバーで必要です)。この選択内容を記録してください。フェーズ 3(認証サービス)、フェーズ 5(サインインページと登録ページ)、フェーズ 8.1(サイト設定)で使用します。

「ローカル認証」の場合 — サイトで使用する登録モードも尋ねます。

質問選択肢
ユーザーはサイトにどのように登録できますか?オープン登録のみ(推奨) — 誰でもユーザー名とパスワードで自由に登録できます、招待のみ — 有効な招待コードを持つユーザーのみ登録でき、直接登録はブロックされます、両方 — ユーザーは自己登録、または招待リンクの利用ができます、登録無効 — 新しいアカウントは作成できません(既存ユーザーのみログインできます)

これが重要な理由 — サーバーは RegistrationManager で次の制御ルールを適用します(crm.solutions.portal/Samples/MasterPortal/Areas/Account/Models/RegistrationManager.cs を参照):

モードEnabledOpenRegistrationEnabledInvitationEnabled動作
オープン登録のみtruetruefalse/registration への直接アクセスが機能します。招待リンクは 404 を返します。
招待のみtruefalsetrue/registration への直接アクセスは 404 を返します。ユーザーは招待リンク → /redeem-invitation → /registration?invitationCode=... の順にアクセスする必要があります。
両方truetruetrueどちらの経路も機能します。招待ではメールアドレスが事前入力され、直接登録は完全に開放されます。
登録無効false(該当なし)(該当なし)すべての登録エンドポイントが 404 を返します。既存のユーザーは引き続きログインできます。

注: Authentication/Registration/RequireInvitationCode 設定は実際のサーバー設定ではありません。サーバーはこの設定を読み取りません。「招待を必須にする」動作は、OpenRegistrationEnabled = false と InvitationEnabled = true の組み合わせだけで実現されます。この設定は作成しないでください。

この選択を REGISTRATION_MODE として保存します。これは以下を決定します。

  • /registration ページを作成するかどうか(Registration disabled 以外の場合は常に作成)
  • /redeem-invitation ページを作成するかどうか(InvitationEnabled が true の場合のみ、つまり Invitation-only または Both)
  • /registration ページで fetchInvitationDetails() を呼び出し、メールアドレスを事前入力するかどうか(InvitationEnabled が true の場合のみ)
  • Phase 8.1 で書き込む決定的なサイト設定のセット
  • CAPTCHA をデフォルトで有効にするか(オープン登録/両方)、無効にするか(招待のみ。招待によってユーザーがすでに絞り込まれているため)

「Microsoft Entra ID」の場合(従業員/社員テナント):テナント情報やクライアント情報は必要ありません。Power Pages はサイトの作成時に、サイトの親テナント用の OIDC サイト設定(Authentication/OpenIdConnect/AzureAD/*)を自動的に構成します。SPA は実行時に window.Microsoft.Dynamic365.Portal.tenant から providerIdentifier(https://login.windows.net/{tenantId}/)を導出するため、ハードコードされた値は不要です。

クレームマッピングもサイレントに自動構成されます。Phase 8.1 では、従業員用 Entra プロバイダーの Authentication/OpenIdConnect/AzureAD/RegistrationClaimsMapping と LoginClaimsMapping に、値 firstname=given_name,lastname=family_name,emailaddress1=upn を常に書き込みます。これについて質問はしません。答えは決定的です。従業員用 Entra ID はデフォルトで v1.0 トークン(発行者 sts.windows.net/{tid}/)を発行しますが、これには email クレームが含まれません。そのため、emailaddress1 の設定に使用できる信頼性の高いクレームは upn のみです。このマッピングがない場合、初回サインイン時に作成される連絡先は oid が関連付けられていても、firstname/lastname/email がすべて空になります(User オブジェクトには contactId が表示されますが、プロフィール項目は空です)。

尋ねる任意の質問は、ボタンの表示名だけにしてください。ユーザーがそのまま選べる適切なデフォルト値を提示します。

質問選択肢
ログインボタンにはどのラベルを表示しますか?(デフォルト:Sign in with Microsoft)(自由入力、デフォルト値あり)

ENTRA_ID_DISPLAY_NAME として保存します(デフォルトは "Sign in with Microsoft")。Phase 3.2 では、type: 'entra-id'、この表示名、そして providerIdentifier なし のエントリーを AUTH_PROVIDERS に追加します(実行時に解決)。

テナント ID が不要な理由: テナント ID は基本的に SPA のボタン接続に使用される値(SPA が /Account/Login/ExternalLogin に POST する値)です。Power Pages は window.Microsoft.Dynamic365.Portal.tenant を通じてサイトの親テナント ID を実行時に公開するため、ユーザーに尋ねなくても SPA が providerIdentifier(https://login.windows.net/{tenantId}/)を構築できます。サーバー側の OIDC 設定はサイト作成時にすでに用意されています。これに対して、Entra External ID ではテナントがサイトの親とは無関係な別の顧客テナントであるため、Portal.tenant から導出できず、ユーザーにテナント ID とサブドメインを入力してもらう必要があります。

ドキュメント:https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings ログインページのレイアウト — 複数の認証プロバイダーが設定されている場合(ローカルと外部プロバイダー1つ、またはプロバイダー2つ以上を含む)、ログインページにはすべてのプロバイダーが表示されます。プロバイダーをどのように配置するか、ユーザーに尋ねてください。

質問ヘッダー選択肢
ログインページのサインインオプションをどのように配置しますか?レイアウト横並び(推奨) — プロバイダーボタンを折り返し可能な行に横並びで配置し、区切り線の下にローカルフォームを表示、縦積み — プロバイダーボタンを幅いっぱいに縦に並べ、区切り線の下にローカルフォームを表示、主要プロバイダーを強調 — 1つのプロバイダーを主要な CTA として目立たせ、ほかのプロバイダーは「その他のサインインオプション」トグルの下に表示し、その下にローカルフォームを表示、タブ形式 — タブでプロバイダーモードを切り替え(プロバイダーが3つ以上の場合に適しており、2つの場合は重厚に感じられます)

この選択を LOGIN_LAYOUT として保存します — フェーズ5.1.1では、これに基づいてログインページを描画します。プロバイダーが1つだけ設定されている場合(例:ローカルプロバイダーなしで Entra External ID のみ)、LOGIN_LAYOUT は関係ありません。AuthButton の「サインイン」は直接 login() を呼び出すため、ログインページは必要ありません。

主要プロバイダーを強調するレイアウトの場合は、追加で質問します。

質問ヘッダー選択肢
どのプロバイダーを主要なサインインオプションとして目立たせますか?主要プロバイダー(設定済みのプロバイダーを選択肢として列挙します。最初の外部プロバイダーが妥当なデフォルトです。)

PRIMARY_PROVIDER_ID として保存します。

次に、対象範囲を決定します。

質問選択肢
どの認証機能が必要ですか?ログインとログアウト + ロールベースのアクセス制御(推奨)、ログインとログアウトのみ、ロールベースのアクセス制御のみ(認証サービスはすでに存在)

次に、オプション機能について尋ねます。

質問選択肢
次のオプション機能を有効にしますか?なし(推奨)、利用規約 — サイトにアクセスする前に利用規約への同意をユーザーに求める

注: 利用規約を選択した場合は、以下の利用規約のフローに従ってください。

招待制の登録はこのリストに含まれません — 上記の登録モードに関する質問で制御されます。登録モードを Invitation-only または Both に設定すると、招待が有効になります。

2要素認証(2FA)は意図的に選択肢として提示していません。 Power Pages に組み込まれている2FAフローはサーバーでレンダリングされ(/Account/Login/SendCode → /Account/Login/VerifyCode)、SPA から割り込むことはできません。コード入力ステップに相当する SPA 用の UI がなく、ログイン途中でユーザーをサーバーページに遷移させると SPA のエクスペリエンスが損なわれます。ユーザーから2FAを明示的に求められた場合は、次のように伝えてください。「Power Pages に組み込まれている2FAには、従来のサーバーでレンダリングされる SendCode/VerifyCode ページが必要ですが、SPA ベースの SPA サイトではまだサポートしていません。外部プロバイダー(Entra ID、Entra External ID、OIDC)の場合は、代わりに ID プロバイダー側で MFA を有効にしてください。Power Pages には透過的に適用され、IdP のブランド体験の中で完結します。ローカルアカウントでは、SPA サイトの2FAは現在サポートされていません。」TwoFactorEnabled、RememberMeEnabled、RememberBrowserEnabled のサイト設定は作成しないでください。

プロフィールページ — サインインしたユーザーが Power Pages Web API を介して自分の連絡先情報を編集できる SPA プロフィールページを作成するか、ユーザーに尋ねてください。これは独自のインフラストラクチャ上の考慮事項(contact エンティティの Web API サイト設定 + Self スコープのテーブル権限)があるため、個別の質問です。

質問ヘッダー選択肢
サインインしたユーザーが Power Pages Web API を介して連絡先情報(名前、携帯電話番号、住所)を編集できるプロフィールページを追加しますか?メールアドレスは読み取り専用で表示されます。プロフィールページいいえ(デフォルト) — プロフィールページなし。ユーザーは SPA から自分の情報を編集できません、はい — ヘッダーのユーザーメニューからアクセスできる、編集フォーム付きの /user-profile SPA ページを作成

INCLUDE_PROFILE_PAGE(ブール値)として保存します。デフォルトは false です。

⚠ 必須のルート名:/user-profile(/profileではありません)。パス/profileは、従来のサーバーレンダリングされたプロフィールページ用にPower Pages サーバーによって予約されています。SPA ルートとして使用すると競合が発生し、ページが壊れます。SPA ルートには必ず/user-profileを使用してください。スキル実行ツールはこのルート名を変更してはなりません。ファイル名も同様に、**src/pages/UserProfile.tsx(Profile.tsxではありません)**です。

trueの場合、フェーズ5.1.9ではsrc/pages/UserProfile.tsx(必須のファイル名)を生成し、authService.tsにgetMyProfile/updateMyProfile(必須の関数名)を追加し、AuthButtonをインラインの[Avatar Name Sign Out]から「My Profile」と「Sign Out」のドロップダウンメニューに変更して、App.tsxに/user-profileルート(必須のパス)を追加します。フェーズ8.1では、Web API のサイト設定(Webapi/contact/enabled = trueおよびWebapi/contact/fieldsにフェーズ8.1で記載されているデフォルトのフィールド一覧をすべて含めます。一部だけではありません)と、Authenticated Users ロールに対するcontactの Self スコープのテーブル権限を設定します。INCLUDE_PROFILE_PAGEの値にかかわらず、AuthButtonのドロップダウン形式が新しいデフォルトとなり、今後のメニュー項目に対応できるようになります。

プロフィールページのデザイン — 意図的にシンプルにしています:

ページには2つのセクションがあります。

  1. Account Details(上部に表示する読み取り専用の情報)— ユーザーのフルネーム(firstnameとlastnameを結合したもの)とメールアドレスのみを表示します。contactId、ロール、サインイン方法、プロバイダー名、最終ログイン日時、その他のアカウントメタデータは表示しないでください。このセクションは最小限にしてください。
  2. 編集フォーム(下部)— 編集できるのは以下のフィールドのみです。Web API経由でメールアドレスを変更すると、認証プロバイダーのクレームマッピングの動作と競合し、外部認証ユーザーにとって予想外の結果となるため、メールアドレスは意図的に編集不可にしています。

デフォルトの編集可能フィールド(フォームには以下の8フィールドを必ず含めてください。メールアドレスやmiddlename、Change Passwordリンクは追加しないでください):

  • 名(firstname)
  • 姓(lastname)
  • 携帯電話(mobilephone)
  • 住所1(address1_line1)
  • 市区町村(address1_city)
  • 都道府県/州(address1_stateorprovince)
  • 郵便番号(address1_postalcode)
  • 国(address1_country)

送信時、すべてのフィールドは任意です。以下は含めないでください。

  • ❌ 編集可能なフィールドとしてのemailaddress1(Account Detailsでのみ読み取り専用で表示)
  • ❌ middlename(フォームをシンプルに保ってください)
  • ❌ 「Change password」リンクまたはボタン(パスワードのリセットはプロフィールページではなく、既存の/forgot-passwordフローで処理します)
  • ❌ プロフィールページの「Sign out」ボタン(サインアウトはヘッダーのAuthButtonドロップダウンからのみ行います)
  • ❌ contactId、userRoles、または名前とメールアドレス以外のアカウントメタデータの表示

Web APIのfieldsサイト設定には、contactidと上記の編集可能フィールド8つを含める必要があります(合計9項目。すべて小文字)。フェーズ8.1で正確な値がそのまま指定されています。

「Yes」を選択するための前提条件:「Authenticated Users」Webロール(またはauthenticatedusersrole: trueフラグを持つ任意のロール)が存在している必要があります。フェーズ1.4でWebロールを確認済みです。該当するロールがない場合は、ロールが割り当てられるまでプロフィールの編集は機能しないことをユーザーに警告し、最初に/create-webrolesを実行するかどうかを確認してください。

認証プロバイダー間の互換性:プロフィールページは、サインイン後の連絡先レコードを操作し、IdP固有のセッション状態を使用しないため、認証プロバイダー(ローカル、Entra ID、Entra External ID、OIDC、ソーシャル)にかかわらず同じように機能します。ページ上でメールアドレスは読み取り専用のため、プロバイダー固有の注意事項を示す必要はありません(メールアドレスの信頼できる情報源は引き続きIdPです)。

「Terms and Conditions」が選択された場合、コンテンツを収集する前にGDPRの前提条件をまず提示してください。基盤となるソリューションがインストールされている場合にのみ、利用規約が機能します。

GDPR の前提条件:サーバーで利用規約を実際に適用するには、次の 3 つすべてを満たす必要があります。

  1. Authentication/Registration/TermsAgreementEnabled = true(これから作成するサイト設定)
  2. Dataverse 環境に msdynce_PortalPrivacyExtensions ソリューションがインストールされていること(IsGdprEnabled はポータルレベルの設定です)
  3. Account/Signin/TermsAndConditionsCopy コンテンツスニペットに空でないテキストが設定されていること(これから作成します)

Privacy Extensions ソリューションがない場合、サーバーは TermsAgreementEnabled を警告なしに無視します。setup-auth スキルは 3 つすべてを設定しますが、Dataverse にソリューションがインストールされていない限り、利用規約はサーバー側で適用されません。

質問する前に Privacy Extensions ソリューションを自動検出してください。 次を実行します。

node "${PLUGIN_ROOT}/scripts/check-solution-installed.js" --solutionName "msdynce_PortalPrivacyExtensions"

スクリプトは JSON を標準出力に出力します。ソリューションが見つかった場合は { "installed": true, "version": "..." }、環境にソリューションがない場合は { "installed": false } を出力します。インフラストラクチャの障害(PAC 環境がない、Azure CLI トークンの期限切れ、solutions テーブルへの読み取り権限がない、ネットワークエラー)が発生した場合は、ゼロ以外の終了コードで終了し、人が読める理由を標準エラー出力に書き込みます。

結果に応じて分岐します。

スクリプトの結果対応
installed: true前提条件の確認を省略し、利用規約の内容収集(次のセクション)に進みます。ユーザーに簡潔に伝えます:「msdynce_PortalPrivacyExtensions v{version} が環境にインストールされていることを確認しました。利用規約が適用されます。」
installed: false利用規約が機能しないことをユーザーにはっきり伝えます:「msdynce_PortalPrivacyExtensions ソリューションが Dataverse 環境にインストールされていません。このソリューションがインストールされるまで、利用規約機能はサーバーで適用されません。ソリューションがない場合、Authentication/Registration/TermsAgreementEnabled は警告なしに無視されます。サイト設定、利用規約ページ、コンテンツスニペットは作成されますが、ソリューションがインストールされるまでゲートは何も行いません。」 次に AskUserQuestion を使用して尋ねます:header「Privacy solution」、question「利用規約を今設定しますか(ソリューションのインストール後に有効化できます)、それともスキップしますか?」、options:「それでも続行する — 利用規約の基盤を作成する。ソリューションは後でインストールします」、「キャンセル — このサイトの利用規約をスキップする」。
スクリプトがゼロ以外の終了コードで終了した(インフラストラクチャの障害)ソリューションを自動検出できなかったことをユーザーに伝えます(理由がわかるよう、標準エラー出力のメッセージを簡潔に含めます。例:「Dataverse に接続できませんでした」「solutions テーブルへの権限がありません」)。手動での質問に切り替えます:question「msdynce_PortalPrivacyExtensions がインストールされているかどうかを自動検出できませんでした。GDPR/Privacy Extensions ソリューションはインストールされていますか?」、options:「はい — ソリューションはインストール済みです(または、これからインストールします)」、「それでも続行する — 利用規約を設定します。ソリューションをインストールするまで適用されないことを理解しています」、「キャンセル — 利用規約は不要です」。

ユーザーが「キャンセル」を選んだ場合(未インストールまたはフォールバックのどちらの場合も):利用規約の分岐全体をスキップし、TermsAgreementEnabled を設定せず、利用規約ページやスニペットを作成しないでください。

それ以外の場合は、利用規約の内容を収集します。サーバーは 4 つのコンテンツスニペットを使用します。スキルはこれらの値を SPA の利用規約ページコンポーネントにハードコードします。ユーザーに尋ねます。

質問ヘッダー選択肢
ユーザーに表示する利用規約のテキストは何ですか?HTML またはプレーンテキストを入力できます。利用規約の内容デフォルトの利用規約を使用する(推奨) — データ利用、アカウントの責任、許容される使用方法を網羅した一般的な利用規約。独自の利用規約を入力する

ユーザーが独自のテキストを入力した場合は、それを使用します。それ以外の場合は、デフォルトの利用規約テンプレートを使用します(デフォルトの内容については authentication-reference.md を参照してください)。

任意のカスタマイズも収集します。

質問ヘッダー選択肢
利用規約ページのラベルをカスタマイズしますか?ラベルデフォルトを使用(推奨) — 見出し:「利用規約」、チェックボックス:「利用規約に同意します。」、ボタン:「確認」、ラベルをカスタマイズする

次の4つの値を保存します。Phase 5で利用規約ページのコンポーネントにハードコードし、Phase 8.1でコンテンツスニペットとして作成します。

  • TERMS_HEADING(デフォルト:「利用規約」)
  • TERMS_CONTENT(デフォルト:一般的な利用規約のHTML)
  • TERMS_AGREEMENT_TEXT(デフォルト:「利用規約に同意します。」)
  • TERMS_BUTTON_TEXT(デフォルト:「確認」)

必要に応じて、TermsPublicationDate についても質問します。

質問ヘッダー選択肢
利用者に利用規約への同意を再度求めるタイミングを指定してください。再同意毎回のログイン時(公開日は設定しない) — 利用者はサインインするたびに利用規約に同意します、公開日を設定 — 利用規約がこの日付より後に更新された場合にのみ、利用者に再同意を求めます

「公開日を設定」を選択した場合は、日付を収集します。形式はISO形式の YYYY-MM-DD(例:2026-01-01)とします。「毎回のログイン時」を選択した場合は、TermsPublicationDate を未設定のままにします。

Phase 1.4でWebロールが見つかった場合は、次の質問もします。

質問選択肢
サイトの保護された領域へのアクセスを許可するWebロールを選択してください。(検出されたWebロール名を選択肢として一覧表示)
2.1.1 任意の詳細設定

必須のプロバイダー詳細を収集した後、詳細設定を行うかどうかをユーザーに尋ねます。

質問選択肢
認証の詳細設定を行いますか?(ログアウトモード、クレームマッピング、セッションタイムアウト、スコープなど)いいえ、デフォルトを使用(推奨)、はい、選択肢を表示

「はい、選択肢を表示」の場合は、選択したプロバイダーに関連する任意設定の表を表示します。プロバイダーの種類に適用される設定のみを表示します。設定する項目ごとに、値を収集します。

ログアウトモード(外部プロバイダーのみ — OIDC、Entra External ID、SAML2、WS-Fed、ソーシャル)

外部プロバイダーを設定する場合は、必ずこの質問をユーザーに提示してください(最もよく使われる詳細設定であり、ユーザー体験にも目に見える影響があります)。ローカル認証のみのサイトでは、この質問を省略します。

2つのログアウトモード:

  • ローカルログアウト(サーバーのデフォルト、よりシンプル) — Power PagesはセッションCookieをクリアし、ユーザーを returnUrl(デフォルトは /)にリダイレクトします。ユーザーはIdPではサインインしたままです。次回、外部プロバイダーのボタンをクリックすると、IdPのSSO Cookieがまだ有効なため、認証情報を再入力せずにサイレントに再サインインします。これは、ほとんどの一般消費者向け/顧客向けサイトでのデフォルトのユーザー体験です。
  • フェデレーションログアウト(RP起点) — Power Pagesはさらに、id_token_hint と post_logout_redirect_uri を指定して、IdPの end_session_endpoint を呼び出します。IdPはユーザーを自身のセッションからもサインアウトさせた後、ブラウザーをサイトにリダイレクトします。ユーザーはシステム間で完全にサインアウトします。次の用途で必要です:共有デバイスを使用するシナリオ、確実なログアウトが求められる規制対象業界、毎回ユーザーに認証情報を再入力させることを明示的に求めるサイト。
質問ヘッダー選択肢
ユーザーがサインアウトしたとき、IdPではどのような処理を行いますか?ログアウトモードローカルログアウトのみ(推奨、サーバーのデフォルト) — Power Pagesのセッションをクリアします。ユーザーはIdPではサインインしたままです、フェデレーションログアウト — IdPでもユーザーをサインアウトさせ、再認証を求めます

「ローカルログアウトのみ」の場合:追加の操作は不要です。Phase 8.1では、サイト設定 RPInitiatedLogout と PostLogoutRedirectUri の書き込みを省略します。サーバーのデフォルト値で正しく処理されます(どちらもデフォルトでは false/未設定です)。

「フェデレーションログアウト」の場合:一緒に設定する必要がある項目が2つあります。RPInitiatedLogout=true のみを設定し、PostLogoutRedirectUri を設定しないと、ユーザーはIdPの「サインアウトしました」ページに取り残されます。これは、稼働中のEntra External IDサイトでのHAR分析によって確認されています。 手順 1 — ログアウト後のリダイレクト URI を収集します(デフォルトはサイトのルートです)。

質問選択肢
IdP でサインアウトした後、ユーザーをどこに移動させますか?(デフォルトはサイトのホームページです。専用の「サインアウト済み」ページがある場合は、別の URL を使用してください。)(自由入力、デフォルト値は {SITE_URL}/)

値を検証します。SITE_URL と同じホスト上の完全修飾 URL である必要があります。POST_LOGOUT_REDIRECT_URI として保存します。

手順 2 — Entra External ID の場合は、アプリ登録で URL を登録するようユーザーに案内します。

必須のアプリ登録手順(Microsoft Entra 管理センターで実施する必要があります):

  1. アプリの登録 → {your app} → 認証に移動します
  2. フロントチャネルのログアウト URLフィールドまでスクロールします(「詳細設定」の下、「暗黙的な許可とハイブリッド フロー」のすぐ上にあります)
  3. **{POST_LOGOUT_REDIRECT_URI}**を入力します — 上記の値と完全に一致させる必要があります
  4. 保存します

これが必要な理由:Entra External ID は、事前に登録されていない post_logout_redirect_uri の値を拒否します(サインイン時のリダイレクト URI と同じセキュリティモデルです)。この登録がない場合、Power Pages が正しく送信していても、IdP はパラメーターを通知なく無視し、ユーザーはサインアウト後に行き場を失います。

その他の OIDC プロバイダー(Okta、Auth0 など)の場合は、同等の登録方法についてプロバイダーのドキュメントを確認してください。多くのプロバイダーでは、アプリの設定にあるこの項目を「ログアウト URL」、「ログアウト後のリダイレクト URI」、「許可されたサインアウト リダイレクト URL」などと呼んでいます。

このオプションを選択すると、フェーズ 8.1 で RPInitiatedLogout=true と PostLogoutRedirectUri={POST_LOGOUT_REDIRECT_URI} の両方が書き込まれます。

OpenID Connect/Entra External ID のオプション設定:

設定説明デフォルト
MetadataAddress明示的な OIDC メタデータエンドポイント URL(Authority の代替です。プロバイダーが特定のメタデータ URL を必要とする場合に使用します)Authority から導出
Scopeスペース区切りの OAuth スコープ(例:openid profile email)openid
ResponseTypeOAuth レスポンスタイプ(code、id_token、code id_token)code id_token
ResponseModeIdP がレスポンスを返す方法(form_post、query、fragment)コードフローでは form_post
RedirectUriコールバック URL を上書きします{site-url}/signin-{ProviderName-lowercased}
PostLogoutRedirectUriIdP でフェデレーションログアウトが完了した後のリダイレクト先 URL です。RPInitiatedLogout=true の場合は必須です。サーバーには RedirectUri の Authority から導出するフォールバックがありますが、別のフラグ(PostLogoutRedirectUriEnabled)により、フォールバックの使用前に明示的なサイト設定が存在することが求められます。明示的な値がない場合、IdP のログアウト URL にこのパラメーターが含まれず、ユーザーは行き場を失います。未設定(サーバーのデフォルト。ただし、正しく設定するには上記のログアウトモードに関する質問を使用してください)
RPInitiatedLogoutend_session_endpoint と id_token_hint を使用して、RP 起点のログアウトを行います。ExternalLogoutEnabled とは併用できません。true の場合、サーバーはこの設定の値にかかわらず ExternalLogoutEnabled を false に強制します。これを直接設定するのではなく、上記の「ログアウトモード」に関する質問を優先してください。そのフローでは、PostLogoutRedirectUri(必須)と Entra アプリ登録の手順が一緒に設定されます。false
RegistrationClaimsMappingカンマ区切りの contactfield=claimtype ペア(JSON ではありません)。連絡先が作成される前の初回サインイン時に 1 回適用されます。Entra External ID の例:firstname=given_name,lastname=family_name,emailaddress1=email。サーバーは形式が正しくないペアを通知なくスキップします。クレームが反映されない場合は、Application Insights で確認してください。なし
LoginClaimsMappingRegistrationClaimsMapping と同じ形式です。毎回のログイン時に適用されます(連絡先フィールドを上書きします)。ユーザーがプロフィールに手動で加えた編集内容を上書きするため、慎重に使用してください。なし
ExternalLogoutEnabledユーザーのログアウト時に IdP からサインアウトします(従来の OWIN サインアウトです。OIDC では RPInitiatedLogout を推奨します)。RPInitiatedLogout=true の場合は false に強制されます。false(サーバーのデフォルト)
RegistrationEnabledこのプロバイダー経由で新規ユーザーが登録できるようにしますtrue
AllowContactMappingWithEmailemail クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動リンクします。 デフォルトは false(常に新しい連絡先が作成されます)。⚠ マルチテナントの Entra External ID:サーバーはこれを強制的に無効にします(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。テナント間ではメールクレームを信頼できないためです。連絡先をリンクする場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ:true の場合、このプロバイダーに被害者のメールアドレスでサインインできる人は誰でも、被害者の連絡先にアクセスできます。メールアドレスを検証するプロバイダーであると信頼できる場合にのみ有効にしてください。false
RequireUniqueEmail登録時にメールアドレスの一意性を必須にしますfalse
UseTokenLifetimeセッション Cookie に IdP トークンの有効期間を使用します未設定
BackchannelTimeoutIdP へのバックチャネル HTTP 呼び出しのタイムアウト(例:00:01:00)00:01:00
RefreshOnIssuerKeyNotFound発行者キーが見つからない場合にプロバイダーのメタデータを更新しますデフォルト
NonceEnabledOIDC トークンの nonce 検証を有効にしますtrue
NonceLifetimeOIDC nonce の有効期間(例:00:10:00)00:10:00
AcrValuesIdP に要求する Authentication Context Class Reference の値なし
PromptOIDC の prompt パラメーター(login、consent、none)。セッションの有効期限が切れたときに再認証を強制するには、login を使用します。なし
Resourceトークンリクエストの Resource パラメーターなし
EmailClaimIdentifierユーザーのメールアドレスとして使用するカスタムクレームタイプ標準のメールクレーム
IssuerFilterテナント間で発行者を照合するワイルドカードパターン(例:https://login.microsoftonline.com/*/v2.0)。マルチテナントアプリでは必須です。これがないと、ホームテナント以外のテナントでは発行者の検証に失敗します。なし
UseUserInfoEndpointforClaimsUserInfo エンドポイントから追加のクレームを取得しますfalse
UserInfoEndpointカスタム UserInfo エンドポイント URL(メタデータにない場合)メタデータから取得
PasswordResetPolicyIdB2C/External ID のパスワードリセット用ユーザーフローポリシー名なし
ProfileEditPolicyIdB2C/External ID のプロフィール編集用ユーザーフローポリシー名なし
DefaultPolicyIdB2C/External ID のデフォルトのサインアップ/サインイン用ポリシー名なし
TokenEndPointAuthenticatedMethodトークンエンドポイントの認証方式(client_secret_post、client_secret_basic、private_key_jwt)。ソブリンクラウドで証明書ベースの認証を使用する場合は、private_key_jwt を使用します。client_secret_post
AllowedDynamicAuthorizationParameters動的に引き渡すことができる OIDC パラメーターのカンマ区切りリストなし
SAML2 のオプション設定:
設定説明デフォルト
AssertionConsumerServiceUrlACS URL(通常は {site-url}/signin-{ProviderName-lowercased})サイト URL から導出
RegistrationClaimsMappingカンマ区切りの contactfield=claimtype ペア。SAML アサーションの種類は URI です(例:firstname=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname,lastname=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname)。初回サインイン時に一度だけ適用されます。なし
LoginClaimsMapping同じ形式です。ログインのたびに適用されます(連絡先フィールドを上書きします)。なし
ExternalLogoutEnabledSAML シングルログアウト(SLO)を有効にしますtrue
RegistrationEnabledこのプロバイダー経由での新規ユーザー登録を許可しますtrue
AllowContactMappingWithEmailemail クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動的にリンクします。 デフォルトは false です(常に新しい連絡先が作成されます)。⚠ マルチテナントの Entra External ID:サーバーによってこの設定は強制的に無効化されます(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。メールクレームはテナント間で信頼できません。連絡先をリンクする場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ: true の場合、このプロバイダーに被害者のメールアドレスでサインインできる人は、被害者の連絡先にアクセスできます。プロバイダーがメールアドレスを検証していると信頼できる場合にのみ有効にしてください。false
AllowCreateNameIdPolicyNameIdPolicy に AllowCreate を含めますtrue
DefaultSignatureAlgorithmSAML リクエストの署名アルゴリズムプロバイダーのデフォルト
SigningCertificateFindTypeリクエストの署名に使用する X509 証明書の検索の種類なし
SigningCertificateFindValue証明書の検索値(例:サムプリント)なし
ExternalLogoutCertThumbprintSLO 応答の署名に使用する証明書のサムプリントなし
SingleLogoutServiceRequestPathSLO リクエストのカスタムパスデフォルト
SingleLogoutServiceResponsePathSLO 応答のカスタムパスデフォルト
ComparisonAuthnContextComparison の種類(exact、minimum、maximum、better)なし
BackchannelTimeoutメタデータ取得のタイムアウト00:01:00
UseTokenLifetimeIdP のトークン有効期間をセッションに使用します未設定
EmailClaimIdentifierユーザーのメールアドレスに使用するカスタムクレームの種類標準のメールクレーム
IssuerFilterマルチテナントの発行者照合に使用するワイルドカードパターンなし

WS-Federation のオプション設定:

設定説明デフォルト
WreplyWS-Fed 応答の返信 URLWtrealm と同じ
Whrホームレルム検出のヒント(例:ドメイン名)なし
SignOutWreplyログアウト後のリダイレクト先 URLサイトのルート
RegistrationClaimsMappingカンマ区切りの contactfield=claimtype ペア。WS-Fed のクレームタイプは通常、SAML URI です(例:firstname=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname)。初回サインイン時に一度適用されます。なし
LoginClaimsMapping同じ形式です。ログインのたびに適用されます(連絡先フィールドを上書きします)。なし
ExternalLogoutEnabledフェデレーションサインアウトを有効にしますtrue
RegistrationEnabledこのプロバイダー経由での新規ユーザー登録を許可しますtrue
AllowContactMappingWithEmailemail クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動的に紐付けます。 デフォルトは false です(常に新しい連絡先が作成されます)。⚠ マルチテナント Entra External ID:サーバーがこの設定を強制的に無効にします(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。テナント間ではメールのクレームを信頼できません。連絡先を紐付ける場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ:true の場合、被害者のメールアドレスを使ってこのプロバイダーにサインインできる人は誰でも、被害者の連絡先にアクセスできます。メールアドレスを検証するプロバイダーであると信頼できる場合にのみ有効にしてください。false
BackchannelTimeoutメタデータ取得のタイムアウト00:01:00
UseTokenLifetimeIdP のトークン有効期間をセッションに使用します未設定
IssuerFilterマルチテナントの発行者照合用のワイルドカードパターンなし

ソーシャル OAuth のオプション設定(Microsoft Account、Facebook、Google):

設定説明デフォルト
Scope要求する OAuth スコープ(スペース区切り)プロバイダーのデフォルト
RegistrationClaimsMappingカンマ区切りの contactfield=claimtype ペア。ソーシャルプロバイダーによってクレームタイプは異なります。Facebook では name/email、Google では given_name/family_name/email を使用します。例:firstname=given_name,emailaddress1=email。初回サインイン時に一度適用されます。なし
LoginClaimsMapping同じ形式です。ログインのたびに適用されます(連絡先フィールドを上書きします)。なし
ExternalLogoutEnabledログアウト時にソーシャルプロバイダーからサインアウトしますtrue
RegistrationEnabledこのプロバイダー経由での新規ユーザー登録を許可しますtrue
AllowContactMappingWithEmailemail クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動的に紐付けます。 デフォルトは false です(常に新しい連絡先が作成されます)。⚠ マルチテナント Entra External ID:サーバーがこの設定を強制的に無効にします(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。テナント間ではメールのクレームを信頼できません。連絡先を紐付ける場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ:true の場合、被害者のメールアドレスを使ってこのプロバイダーにサインインできる人は誰でも、被害者の連絡先にアクセスできます。メールアドレスを検証するプロバイダーであると信頼できる場合にのみ有効にしてください。false
BackchannelTimeoutOAuth トークン交換のタイムアウト00:01:00

ローカル認証のオプション設定:

設定説明デフォルト
Authentication/Registration/OpenRegistrationEnabledセルフ登録を許可しますtrue
Authentication/Registration/EmailConfirmationEnabled登録時にメール確認を必須にしますfalse
Authentication/Registration/RememberMeEnabledログインフォームに「ログイン状態を保持する」チェックボックスを表示しますfalse
Authentication/Registration/ResetPasswordEnabledパスワードを忘れた場合の手続きを有効にしますtrue
Authentication/Registration/ResetPasswordRequiresConfirmedEmailパスワードのリセットを許可する前にメール確認を必須にしますfalse
Authentication/Registration/RequireUniqueEmailメールアドレスの重複を禁止しますfalse
Authentication/Registration/TermsAgreementEnabled登録時に利用規約への同意を必須にします。登録完了前に、サーバーは利用規約ページへリダイレクトします。false
Authentication/Registration/IsCaptchaEnabledForRegistration登録フォームに CAPTCHA を表示しますfalse
Authentication/Registration/TriggerLockoutOnFailedPasswordログイン失敗が一定回数を超えるとアカウントをロックしますtrue
Authentication/Registration/DenyMinors未成年者と識別されたユーザーの登録を拒否しますfalse
Authentication/Registration/DenyMinorsWithoutParentalConsent保護者の同意がない未成年者の登録を拒否します(GDPR の有効化が必要です)false

セッション/Cookie の設定(すべてのプロバイダー):

設定説明デフォルト
Authentication/ApplicationCookie/ExpireTimeSpanセッションのタイムアウト時間(例:1 時間の場合は 01:00:00)01:00:00
Authentication/ApplicationCookie/SlidingExpirationリクエストごとに Cookie を更新しますtrue
Authentication/ApplicationCookie/AbsoluteSlidingExpireTimeSpanアクティビティの有無にかかわらず適用される、セッションの絶対最大有効期間なし
Authentication/ApplicationCookie/CookieNameカスタムセッション Cookie 名Power Pages のデフォルト
Authentication/ApplicationCookie/CookieDomainCookie のドメイン範囲現在のドメイン
Authentication/ApplicationCookie/CookiePathCookie のパス範囲/
Authentication/ApplicationCookie/CookieHttpOnlyセッション Cookie への JavaScript アクセスを防止しますtrue
Authentication/ApplicationCookie/CookieSecureセッション Cookie に HTTPS を必須にしますtrue
Authentication/ApplicationCookie/LoginPathカスタムログインページのパス/Account/Login/Login
Authentication/ApplicationCookie/SecurityStampValidator/ValidateIntervalユーザーのセキュリティスタンプを検証する間隔(例:00:30:00)デフォルト

グローバル認証の切り替え設定(すべてのプロバイダー):

設定説明デフォルト
Authentication/Registration/LoginButtonAuthenticationTypeログインボタンのデフォルトプロバイダーなし(すべて表示)
Authentication/Registration/AzureADLoginEnabledAzure AD(Entra ID)ログインを有効/無効にしますtrue
Authentication/Registration/ExternalLoginEnabledすべての外部 ID プロバイダーのログインを有効/無効にしますtrue
Authentication/Registration/SignOutEverywhereEnabledログアウト時にユーザーのセキュリティスタンプを更新して、すべてのデバイスの全セッションを無効にしますfalse

ユーザーが設定を希望する各項目について、Phase 8.1 で必要な設定と併せて create-site-setting.js を使用し、サイト設定を作成します。

2.1.2 IDP アプリ登録を設定する(最初にプロバイダーのドキュメントを確認)

OIDC プロバイダー(Okta、Auth0、Entra External ID、その他の OIDC)に適用されます。SAML2、WS-Federation、ソーシャル認証、またはローカル認証の場合は、Phase 2.1 の手順を使用してください。

Power Pages では、サイトに接続する OIDC のアプリ登録が IDP に必要です。設定方法は ID プロバイダーによって異なるため、まずプロバイダーが公開している最新のドキュメントを確認してください。

最初にお読みください:${PLUGIN_ROOT}/skills/setup-auth/references/idp-provisioning-reference.md — Power Pages と IDP の契約(Redirect URI、シークレット不要の code id_token フロー)、およびIDP ごとの公式ドキュメントへのリンクです。

ステップ A — プロバイダーのドキュメントを確認します(必ず最初に行います)。 ユーザーが希望する IDP がわかったらすぐに、該当プロバイダーの公式ドキュメントを(参照リンクから。Microsoft の IDP については Learn MCP の microsoft_docs_search/microsoft_docs_fetch を使用して)読み、次の 2 点を確認します。(1) OIDC アプリを登録して構成する正確な手順、(2) アプリの構成に使用できる CLI(Okta、Auth0、Entra External ID はそれぞれ CLI を提供しています)。これより前に、ほかのステップに進んではいけません。 また、コンソールのパスやエンドポイントを決して作り上げてはいけません。

ステップ B — 設定方法を選びます。 方法は 2 つあります。どちらも提示してください。

  • ガイド付き(デフォルト、推奨)— プロバイダーのドキュメントを読み、プロバイダー独自のコンソールで実行する手順をユーザーに案内します。常に利用できます。
  • ユーザーに代わって構成する — プロバイダーがアプリ構成用の CLI を提供している場合に選択できます。Okta、Auth0、Entra External ID はいずれも CLI を提供しているため、必ずこの方法も提示してください。 CLI がまだインストールされていなくても、提示を控える理由にはなりません。CLI のインストールとサインインもこの方法に含まれます。

このような CLI を提供していないプロバイダーの場合は、ガイド付きで進めます。

プロバイダーがこのような CLI を提供している場合(Okta、Auth0、Entra External ID では必ず該当します)は、次の質問をします(該当しない場合は質問せず、ガイド付きで進めます)。

質問ヘッダー選択肢
{provider} アプリの登録をどのように設定しますか?設定ガイド付き(推奨) — {provider} コンソールで実行する手順を案内します、ユーザーに代わって構成 — CLI を使って {provider} で設定します

ステップ C — Power Pages が必要とするアプリ構成を提示し、承認を得ます。 Power Pages のデフォルトはシークレット不要の code id_token フローです。そのため、アプリはクライアントシークレットなしの Web アプリケーションです。目標とする構成を提示し、承認または変更を求めます。

  • アプリの種類: Web アプリケーション — クライアントシークレットなし
  • サインインフロー: code id_token(アプリは IDP から ID トークンを発行される必要があります)
  • Redirect URI: <REDIRECT_URI>(大文字・小文字と signin- プレフィックスを含め、完全一致)
  • スコープ: openid profile email

ガイド付きの場合、このチェックリストをユーザーがコンソールで適用します。ユーザーに代わって構成する場合は、この仕様を適用します。

ステップ D — 順番に実行し、各ステップを検証します。一度にまとめて実行してはいけません。 1 つのステップを実行して検証してから、次に進みます。

  1. アプリを登録する → Client ID を取得・検証します。
  2. Redirect URI を設定する → <REDIRECT_URI> と完全に一致することを検証します。
  3. ID トークンの発行(code id_token)を有効にする → 検証します。
  • ガイド付き: ユーザーにコンソールで各ステップを実行するよう案内し、次に進む前に結果を確認します。
  • ユーザーに代わって構成する: 各ステップでプロバイダーの CLI/API を実行し、次に進む前にオブジェクトを読み戻して検証します。

Okta: Implicit(Hybrid) と Allow ID Token を有効にし、ユーザーにアプリを割り当ててください(割り当てない場合、access_denied「Policy evaluation failed」が発生します)。また、Org authorization server(https://{domain})を優先してください。参照資料の Okta セクションをご覧ください。

🚦 ゲート(同意 · setup-auth:2.1.2.provision-idp): ユーザーの ID プロバイダーで、ユーザーに代わって実際のアプリ登録を構成する前に、明示的な実行可否の確認を得ます。

トリガー: ユーザーがユーザーに代わって構成を選択し、プロバイダーが対応している場合に、IDP でユーザーに代わって実際の変更を行おうとしているとき。 確認する理由: ユーザーの IDP テナントに実際のオブジェクトを作成・構成するため、明示的な同意なしに実行してはいけません。ガイド付きの設定では、ユーザー自身が変更を行うため、このゲートには達しません。 キャンセルした場合: 何も起こりません。アプリは構成されず、ユーザーは代わりにガイド付きの設定に切り替えられます。 | 質問 | 選択肢 | |----------|---------| | 今すぐ {provider} で {provider} アプリの登録を設定しますか? | はい — 設定を任せる、いいえ — ガイド付きセットアップに切り替える |

「いいえ」の場合:ガイド付きフローに切り替えます。

ステップ E — 登録を開始します。 外部サインインには、サイトの登録制御が適用されます。推奨されるデフォルト設定を確認します — 顧客向けプロバイダー(Auth0 / Okta / Entra External ID)では オープン、社内向け Entra ID では 制限付き:

質問ヘッダー選択肢
{provider} を通じてサインインしたユーザー全員のアカウントを自動作成しますか、それとも事前にプロビジョニングされたユーザーのみを対象にしますか?登録オープン — セルフサービス(顧客向けサイトに推奨) — 認証済みユーザーの連絡先を自動作成、制限付き — 事前にプロビジョニングされたユーザーのみ — メールアドレスが既存の連絡先と一致するユーザーのみを許可

OPEN_REGISTRATION_CHOICE として保存します。フェーズ 8.1 で Authentication/Registration/OpenRegistrationEnabled(オープンの場合は true、制限付きの場合は false)を書き込みます。これを、フェーズ 2.1 ですでに収集した連絡先のリンク設定(AllowContactMappingWithEmail)と組み合わせます。

ステップ F — フェーズ 8.1 に渡す結果を記録します。 アプリのセットアップ方法にかかわらず、フェーズ 8.1 でサイト設定として書き込む値を記録します(両方のフローで同じです):ClientId、Authority(および AuthenticationType = Authority)、MetadataAddress(プロバイダーのディスカバリーエンドポイント — IDP から取得)、RedirectUri。ClientSecret は不要です — シークレットなしのフローでは必要ないため、Entra External ID と同様にフェーズ 8.1.1(Key Vault)はスキップします。リファレンスの「生成されるサイト設定」テーブルを参照してください。

2.2 承認用の計画を提示する

実装計画をインラインで提示します:

  • 作成するファイル(認証サービス、型、認可ユーティリティ、コンポーネント)
  • 認証 UI をサイトのナビゲーションに統合する方法
  • 保護するルート/コンポーネントと、それぞれに設定するロール
  • 設定が必要なサイト設定(Authentication/Registration/ProfileRedirectEnabled = false)

🚦 ゲート(計画 · setup-auth:2.2.plan-approval): ファイルを書き込む前に、認証実装計画の最終承認を得ます。

トリガー: フェーズ 2.2 で計画全体をインラインで提示しました。 確認する理由: 誤ったコンポーネントが生成されたり、サイト設定が書き込まれたり、ProfileRedirectEnabled が切り替わったりすると、修正は可能ですが、手戻りが増えます。 キャンセル時に残るもの: 何もありません — 認証ファイルはまだ書き込まれていません。

AskUserQuestion を使って承認を得ます:

質問選択肢
認証と認可の実装計画は以上です。進めてもよろしいですか?承認して続行(推奨)、変更したい

「承認して続行」の場合:フェーズ 3 に進みます。

「変更したい」の場合:変更したい内容をユーザーに尋ね、計画を修正して、再度承認を求めます。

出力
  • 認証の範囲を確認済み(ログイン/ログアウト、ロールベースのアクセス制御、またはその両方)
  • 対象の Web ロールを選択済み
  • ユーザーが実装計画を承認済み

フェーズ 3:認証サービスの作成

目標: ローカル開発用のモックサポートを含む、認証サービス、型宣言、フレームワーク固有の認証フック/コンポーザブルを作成します。

リファレンス:${PLUGIN_ROOT}/skills/setup-auth/references/authentication-reference.md

作業
3.1 型宣言の作成

Power Pages ポータルオブジェクトとユーザーの型定義を含む src/types/powerPages.d.ts を作成します:

  • PowerPagesUser インターフェース — userName、firstName、lastName、email、contactId、userRoles[]
  • PowerPagesPortal インターフェース — User、version、type、id、geo、tenant など
  • Microsoft.Dynamic365.Portal 用の Window インターフェースのグローバル拡張
3.2 認証サービスの作成

検出されたフレームワークと選択された ID プロバイダーに基づいて、認証サービスのファイルを作成します。

エントリが1つだけの場合でも、必ずAUTH_PROVIDERS配列パターンを使用してください。 単一のAUTH_PROVIDER定数は生成しないでください。配列パターンなら、後から2つ目のプロバイダー(たとえば、2つ目のEntra External IDテナントや、ローカルと外部プロバイダーの組み合わせ)を追加する場合、配列に追加するだけで済み、構造を変更する必要がありません。単一定数パターンでは複数のプロバイダーを表現できず、再実行時に設定済みのプロバイダーがひそかに削除されるという種類のバグを防げます。

配列には、以下を含める必要があります。

  • Phase 1.5のEXISTING_PROVIDERSに含まれるすべてのプロバイダー(ユーザーのMERGE_MODEの選択に基づいてマージしたもの)
  • Phase 2.1でユーザーが追加した新しいプロバイダー

各プロバイダーには、安定したid(例:entra-external-id-customer、entra-external-id-employee、local)を使用し、再実行後もReactのキーとswitch文が変わらないようにしてください。

すべてのフレームワーク:これらの関数と型を含むsrc/services/authService.tsを作成してください。

  • AuthProviderType — 文字列のユニオン型:'local' | 'oidc' | 'entra-id' | 'saml2' | 'ws-federation' | 'social'
  • AuthProviderConfig — id、type、displayName、省略可能な providerIdentifier('oidc'/'saml2'/'ws-federation'/'social' の場合は必須です。'entra-id' の場合は省略してください。実行時に解決されます)、省略可能な loginByEmail(ローカル認証専用)を持つインターフェース
  • AUTH_PROVIDERS: AuthProviderConfig[] — 配列です(設定済みのプロバイダーごとに1つのエントリーを持ち、ログインページでの表示順に並びます)
  • LOCAL_PROVIDER — エクスポートされるヘルパー:AUTH_PROVIDERS.find(p => p.type === 'local')(ローカルプロバイダーがない場合は undefined)
  • EXTERNAL_PROVIDERS — エクスポートされるヘルパー:AUTH_PROVIDERS.filter(p => p.type !== 'local')
  • getCurrentUser() — window.Microsoft.Dynamic365.Portal.User から読み取ります
  • isAuthenticated() — ユーザーが存在し、userName を持つか確認します
  • getTenantId() — window.Microsoft.Dynamic365.Portal.tenant(サイトの親テナントの GUID)から読み取ります。まだ値が設定されていない場合は undefined を返します。
  • getAuthProvider() — 非推奨です。後方互換性のため、最初のローカルプロバイダー、またはローカルプロバイダーがない場合は全体の最初のプロバイダーを返します。代わりに AUTH_PROVIDERS を直接参照してください。
  • fetchAntiForgeryToken() — /_layout/tokenhtml から取得し、HTML レスポンスを解析します
  • resolveProviderIdentifier(provider) — /Account/Login/ExternalLogin に provider として POST する値を返します。type='entra-id' の場合、実行時に https://login.windows.net/${getTenantId()}/ を導出します。それ以外の外部プロバイダーの場合は、provider.providerIdentifier を返します。Entra ID のテナント ID を AUTH_PROVIDERS 配列にハードコードしないでください。このリゾルバーが処理します。
  • loginExternal(providerIdentifier, returnUrl?, invitationCode?) — 外部プロバイダー用に /Account/Login/ExternalLogin へフォーム POST します
  • loginLocal(credential, password, rememberMe?, returnUrl?, invitationCode?) — ローカル認証用に /SignIn へ fetch POST します
  • loginWithProvider(provider, { returnUrl?, invitationCode?, credentials? }) — ルーター:provider.type に応じて loginLocal() または loginExternal() を呼び出します。resolveProviderIdentifier() を使用するため、Entra ID の実行時の解決も透過的に処理されます。UI コンポーネントから呼び出す関数です。
  • logout(returnUrl?) — /Account/Login/LogOff へリダイレクトします
  • getAuthError() — サーバー側の認証エラーによるリダイレクトのクエリパラメーター ?message= または ?error= を解析し、ユーザー向けのエラーメッセージを返します
  • getSessionExpiredMessage() — ?sessionExpired=true を確認し、セッション期限切れのメッセージを返します
  • parseServerErrors(html) — ローカル認証では必須です。 サーバーの HTML レスポンスから検証エラー(.validation-summary-errors li、.alert-danger li、.field-validation-error)を解析します。ログインと登録で使用し、SPA にサーバーエラーを表示します。
  • register(fields, returnUrl?, invitationCode?) — ローカル認証を設定している場合に必須です。 偽造防止トークン、メールアドレスまたはユーザー名(Phase 2.1 で選択した LocalLoginByEmail に応じます)、パスワード、確認用パスワード、および省略可能な招待コードを含めて、登録フォームを /Account/Login/Register に POST します。LocalLoginByEmail が true の場合は Email フィールドを送信します。false の場合は Username フィールドを送信します。実装の全容については authentication-reference.md を参照してください。
  • forgotPassword(email) — ローカル認証を設定している場合に必須です。 Email と偽造防止トークンを含む MVC フォームを /Account/Login/ForgotPassword に POST します。サーバーからパスワードリセットメールが送信されます。ログインと同様に fetch() を使用します。プロミスを返します。成功時(.then())には「メールを確認してください」という確認メッセージを表示し、失敗時(.catch())にはエラーを表示します。
  • resetPassword(userId, code, password, confirmPassword) — ローカル認証を設定している場合に必須です。 UserId、Code、Password、ConfirmPassword、__RequestVerificationToken を含む MVC フォームを /Account/Login/ResetPassword に POST します。UserId と Code は、メールのリセットリンクによって設定された URL クエリパラメーターから取得します。成功すると /login?message=password_reset_success にリダイレクトします。
  • TermsRequiredError — 利用規約が有効な場合に必須です。 ログインまたは登録後にサーバーが利用規約ページへリダイレクトするとスローされるカスタムエラークラスです。ログイン/登録ページでこれをキャッチし、SPA の /terms ページへ移動します。
  • acceptTerms(returnUrl?) — 利用規約が有効な場合に必須です。 サーバーの利用規約ページを取得(GET /Account/Login/TermsAndConditions)して偽造防止トークンを取得した後、同意内容(IsTermsAndConditionsAccepted=true、IsFacebook=False、UseExternalSignInAsync=False、IsInternalAADUser=False)を POST します。レスポンス URL を動的に使用します(サーバーは /Account/Login/TermsAndConditions または /TermsAndConditions から利用規約ページを提供する場合があります)。
  • getUserDisplayName() — フルネームを優先し、なければ userName を返します
  • getUserInitials() — アバター表示用です ログイン時および登録時の利用規約検出: loginLocal() と register() の両方で、fetch の完了後に response.url.includes('TermsAndConditions') を確認する必要があります。サーバーはフローに応じて異なる URL にリダイレクトします。
  • ログイン: /Account/Login/TermsAndConditions にリダイレクトします
  • 登録: /TermsAndConditions?ReturnUrl=%2F にリダイレクトします

どちらも response.url.includes('TermsAndConditions') で検出できます。検出した場合は、TermsRequiredError をスローします。サーバーは DeferredLocalLoginCookie も設定します。これは、利用規約が承諾されるまでセッションの作成を保留します。

重大 — ローカルログインと登録には form.submit() ではなく fetch() を使用してください。 form.submit() を使用するとページ全体が遷移するため、サーバーがエラーを返した場合、ユーザーは SPA から離れてサーバーレンダリングされたエラーページを表示することになります。代わりに fetch() を使用すれば、ユーザーは SPA 内にとどまります。成功時(リダイレクト時)は window.location.href で遷移し、失敗時(HTML を含む 200 応答)は parseServerErrors() でエラーを解析してスローすることで、ページコンポーネントにインライン表示できます。完全な実装については authentication-reference.md を参照してください。

ログインフローはプロバイダーの種類によって異なります:

  • Microsoft Entra ID:プロバイダーに https://login.windows.net/{tenantId}/ を指定して、/Account/Login/ExternalLogin にフォーム POST します
  • Entra External ID:External ID の AuthenticationType をプロバイダーに指定して、/Account/Login/ExternalLogin にフォーム POST します(サイト設定 Authentication/OpenIdConnect/{provider}/AuthenticationType で設定)。内部では OpenID Connect を使用し、External ID テナントのオーソリティ URL を使います。
  • OpenID Connect:OIDC の AuthenticationType をプロバイダーに指定して、/Account/Login/ExternalLogin にフォーム POST します(サイト設定 Authentication/OpenIdConnect/{provider}/AuthenticationType で設定)
  • SAML2:SAML2 の AuthenticationType をプロバイダーに指定して、/Account/Login/ExternalLogin にフォーム POST します(サイト設定 Authentication/SAML2/{provider}/AuthenticationType で設定)
  • WS-Federation:WS-Federation の AuthenticationType をプロバイダーに指定して、/Account/Login/ExternalLogin にフォーム POST します(サイト設定 Authentication/WsFederation/{provider}/AuthenticationType で設定)
  • ローカル認証:Password ではなく PasswordValue、/_layout/tokenhtml から取得した偽造防止トークン、および任意の RememberMe を使用して、/SignIn にフォーム POST します。LocalLoginByEmail が true の場合は Email フィールドを送信し、それ以外の場合は Username フィールドを送信します。注:ログインエンドポイントでは /SignIn と PasswordValue を使用します。これは、/Account/Login/Register と Password を使用する登録エンドポイントとは異なります。ExternalLogin エンドポイントは使用しません。
  • Microsoft Account:プロバイダーに urn:microsoft:account を指定して、/Account/Login/ExternalLogin にフォーム POST します
  • Facebook:プロバイダーに Facebook を指定して、/Account/Login/ExternalLogin にフォーム POST します
  • Google:プロバイダーに Google を指定して、/Account/Login/ExternalLogin にフォーム POST します

重大: Power Pages の認証はサーバー側で行われます(セッションクッキーを使用)。外部ログインフローでは、サーバーにフォームを POST し、サーバーが ID プロバイダーにリダイレクトします。ローカルログインでは、認証情報をサーバーに直接 POST します。クライアント側でのトークン管理はありません。fetchAntiForgeryToken() の呼び出しで取得するのはフォーム POST 用の CSRF トークンであり、ベアラートークンではありません。

シークレットの管理: 認証サービスのコードや、ソース管理にコミットされるファイルに ClientSecret、AppSecret、その他の認証情報の値を絶対に含めないでください。providerIdentifier フィールドは公開識別子(URL または名前)であり、シークレットではありません。実際のシークレットは Power Pages 管理センターで設定する必要があります。

サーバーレンダリングされたページへの対応: 外部ログインフローでは、特定のフロー(初回登録時の ExternalLoginConfirmation、2FA の SendCode/VerifyCode、利用規約の承諾時の TermsAndConditions など)で、Power Pages サーバーがサーバーレンダリングされたページにリダイレクトする場合があります。これらは SPA ではインターセプトできないサーバー側の判断です。こうしたリダイレクトを最小限に抑えるには、次のようにします。

  • Authentication/Registration/OpenRegistrationEnabled が正しく設定されていることを確認します。true の場合、新しい外部ユーザーは ExternalLoginConfirmation ページなしで自動登録されます
  • 明示的に必要な場合を除き、TermsAgreementEnabled が false になっていることを確認します。そうでない場合、初回ログイン時にサーバーでレンダリングされた利用規約ページが毎回表示されます
  • 2FA フローでは、サーバーが SendCode ページと VerifyCode ページをレンダリングします。これらを SPA のコードで置き換えることはできません
  • ユーザーがサーバーでレンダリングされたページから戻ったら、SPA は認証状態の変化(getCurrentUser())を確認し、それに応じて UI を更新します
  • 認証サービスの useAuth フックは、SPA の外部で発生したセッションの変更を反映するため、マウント時に refresh() を呼び出します

ローカル認証では、エラー処理はすべてクライアント側で行われます。login() 関数と register() 関数は form.submit() ではなく fetch() を使用するため、ユーザーは SPA 内にとどまります。サーバーエラーは HTML レスポンスから parseServerErrors() で解析され、UI にインライン表示できるようにスローされます。

3.3 フレームワーク固有の認証フック/コンポーザブルを作成する

検出されたフレームワークに基づいて、次のように作成します。

  • React:src/hooks/useAuth.ts を作成します。{ user, isAuthenticated, isLoading, displayName, initials, login, logout, refresh } を返すカスタムフックです
  • Vue:src/composables/useAuth.ts を作成します。ref、computed、onMounted を使用し、リアクティブな認証状態を返すコンポーザブルです
  • Angular:src/app/services/auth.service.ts を作成します。ユーザー状態に BehaviorSubject を使用するインジェクタブルサービスです
  • Astro:src/services/authService.ts のみを作成します(フレームワーク固有のラッパーは不要です。コンポーネントからサービスを直接使用します)
3.4 ローカル開発用のモックデータを追加する

認証は Power Pages から配信されている場合にのみ機能し、ローカルの npm run dev では機能しません。認証サービスに開発用のモックパターンを追加します。

// In development (localhost), return mock user data for testing
const isDevelopment = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';

モックでは、開発者がローカルでロールベースの UI をテストできるよう、ロールを設定可能な偽のユーザーを返します。

3.5 セッション維持フックの作成

SPA のセッション有効期限切れの問題: SPA ではページ遷移がクライアント側で行われるため、サーバーへのリクエストは発生しません。セッション Cookie の SlidingExpiration が更新されるのは、ブラウザーがサーバーにリクエストを送信したときだけです。セッション維持の仕組みがないと、ユーザーが SPA をアクティブに利用している間も、セッションは気付かれないまま期限切れになります。デフォルトの ExpireTimeSpan は 24 時間で、更新はその半分の時点(12 時間後)に行われますが、より短く設定することもできます。

認証プロバイダーを問わず動作します — ローカル認証と外部認証の両方に対応します。 Power Pages は、ユーザーのサインイン方法(ローカルパスワード、Entra External ID、汎用 OIDC、SAML2、ソーシャルログイン)に関係なく、同じ ApplicationCookie セッション Cookie を発行します。セッション維持の仕組みはこの Cookie に対して動作するため、同じフックですべてのプロバイダーに対応できます。isAuthenticated() は window.Microsoft.Dynamic365.Portal.User を読み取り、認証済みのすべてのユーザーに対して値が設定されます。プロバイダーごとの分岐は不要です。

外部プロバイダー — 独立した 2 つの有効期限。 外部認証では、Power Pages セッション Cookie と IdP トークン(例:Entra External ID の ID トークン/リフレッシュトークン)の有効期限はそれぞれ独立しています。SPA が維持する必要があるのは Power Pages セッションです。IdP トークンは SPA からは見えません。Power Pages セッションが期限切れになり、ユーザーが /login?sessionExpired=true にリダイレクトされたとき、外部プロバイダーボタンをクリックすると IdP との間で認証処理が行われます。ただし、IdP の SSO Cookie がまだ有効であれば(通常は有効です)、この処理はサイレントに行われ、認証情報の再入力は必要ありません。ユーザーはサインイン済みの状態でサイトに戻ります。これが想定されるユーザーエクスペリエンスであり、追加の処理は不要です。

セッション Cookie を更新するため、定期的に /_layout/tokenhtml に ping を送信するセッション維持フックを作成します。

  • React:src/hooks/useSessionKeepAlive.ts を作成します
  • Vue:src/composables/useSessionKeepAlive.ts を作成します
  • Angular:src/app/services/session-keepalive.service.ts を作成します

フックには次の要件があります。

  • セッションタイムアウトに基づく SESSION_EXPIRE_MS 定数を定義します。
    • Phase 2.1.1 でユーザーが ApplicationCookie/ExpireTimeSpan をカスタム設定した場合は、その時間をミリ秒に変換します
    • デフォルトを使用する場合は、24 * 60 * 60 * 1000(24 時間)を使用します
  • セッションタイムアウトからタイミングを算出します。間隔をハードコードしないでください。
    • intervalMs = min(SESSION_EXPIRE_MS / 3, 15 * 60 * 1000) — セッションタイムアウトの 1/3 の間隔で ping を送信し、上限は 15 分とします。これにより、SlidingExpiration の中間時点での更新より十分前に ping が行われます。
    • idleTimeoutMs = min(SESSION_EXPIRE_MS * 0.9, 30 * 60 * 1000) — セッションタイムアウトの 90% の間アイドル状態が続いたら ping を停止し、上限は 30 分とします。
    • 例:10 分のセッションでは、intervalMs=3.3 分、idleTimeoutMs=9 分です。24 時間のセッションでは、intervalMs=15 分、idleTimeoutMs=30 分です。
  • 算出した間隔で fetchAntiForgeryToken() を使って /_layout/tokenhtml に ping を送信します
  • ユーザーが認証済みの場合(isAuthenticated())にのみ ping を送信します
  • ブラウザーのタブが表示されている場合(document.visibilityState !== 'hidden')にのみ ping を送信します
  • マウス、キーボード、タッチ、スクロールの操作を追跡し、idleTimeoutMs の間アイドル状態が続いたら ping を停止します。セキュリティのため、セッションは自然に期限切れにします
  • セッションの期限切れを検出します。ping が失敗した場合は onSessionExpired コールバックを呼び出し、アプリが ?sessionExpired=true を付けてログインページにリダイレクトできるようにします
  • 開発モードでは、維持する実際のセッションがないため、処理を完全にスキップします

Layout コンポーネントにフックを組み込み、すべてのページで実行されるようにします。/login?sessionExpired=true に遷移する onSessionExpired コールバックを渡します。ログインページでは、すでに getSessionExpiredMessage() を使って ?sessionExpired=true を処理しています。

出力
  • Power Pages の型定義を含む src/types/powerPages.d.ts を作成
  • ログイン/ログアウト関数を含む src/services/authService.ts を作成
  • フレームワーク固有の認証フック/コンポーザブルを作成
  • セッション維持フックを作成し、Layout に組み込み
  • ローカル開発用のモックデータを含める

フェーズ 4:認可ユーティリティを作成する

目標: ロール確認ユーティリティと、フレームワーク固有の認可コンポーネント(ガード、ディレクティブ、ラッパーコンポーネント)を作成します。

参照:${PLUGIN_ROOT}/skills/setup-auth/references/authorization-reference.md

アクション
4.1 コア認可ユーティリティを作成する

src/utils/authorization.ts を作成し、以下を実装します。

  • getUserRoles() — 現在のユーザーのロール名の配列を返します
  • hasRole(roleName) — 大文字と小文字を区別しない単一ロールの確認を行います
  • hasAnyRole(roleNames) — 複数のロールを対象に OR 確認を行います
  • hasAllRoles(roleNames) — 複数のロールを対象に AND 確認を行います
  • isAuthenticated() — 認証サービスから再エクスポートします
  • isAdmin() — 「Administrators」ロールを確認します
  • hasElevatedAccess(additionalRoles) — 管理者または指定されたロールを確認します
4.2 フレームワーク固有の認可コンポーネントを作成する

検出されたフレームワークに応じて、以下を作成します。

React:

  • src/components/RequireAuth.tsx — 認証済みユーザーにのみ子要素を表示し、オプションでログイン案内の代替表示を設定できます
  • src/components/RequireRole.tsx — 指定されたロールを持つユーザーにのみ子要素を表示し、requireAll モードをサポートします
  • src/hooks/useAuthorization.ts — { roles, hasRole, hasAnyRole, hasAllRoles, isAuthenticated, isAdmin } を返すフック

Vue:

  • src/composables/useAuthorization.ts — 算出されたロールとロール確認関数を備えたコンポーザブル
  • src/directives/vRole.ts — SPA 以外でのロールベースの表示制御に使用する v-role ディレクティブ

Angular:

  • src/app/guards/auth.guard.ts — 必要なロールをルートデータに指定できる CanActivateFn
  • src/app/directives/has-role.directive.ts — 構造ディレクティブ *appHasRole="'RoleName'"

Astro:

  • src/utils/authorization.ts のみ(コンポーネントのスクリプトから直接使用)
4.3 セキュリティに関する注意事項

認可ユーティリティの先頭に、以下のコメントを追加します。

// IMPORTANT: Client-side authorization is for UX only, not security.
// Server-side table permissions enforce actual access control.
// Always configure table permissions via /integrate-webapi.
出力
  • ロール確認関数を含む src/utils/authorization.ts を作成
  • フレームワーク固有の認可コンポーネント(ガード、ディレクティブ、またはラッパーコンポーネント)を作成
  • セキュリティに関する注意コメントを追加

フェーズ 5:認証 UI を作成する

目標: ログイン/ログアウトボタンコンポーネントを作成し、サイトのナビゲーションに組み込みます。

アクション
5.1 認証ボタンコンポーネントを作成する

検出されたフレームワークに応じて、ログイン/ログアウトボタンコンポーネントを作成します。

  • React:src/components/AuthButton.tsx + src/components/AuthButton.css
  • Vue:src/components/AuthButton.vue
  • Angular:src/app/components/auth-button/auth-button.component.ts + テンプレート + スタイル
  • Astro:src/components/AuthButton.astro

コンポーネントには以下の機能が必要です。

  • ユーザーが未認証の場合、「サインイン」ボタンを表示する
  • 認証済みの場合、ユーザーの表示名、アバター(イニシャルを使用)、および「サインアウト」ボタンを表示する
  • 認証状態の確認中は読み込み状態を表示する
  • サイトの既存のデザインに合わせてスタイルを設定する(既存の CSS 変数/テーマを確認)
5.1.1 サインインページを作成する

ルート名の指定 — サーバーとの競合を避ける: Power Pages では、サーバーでレンダリングされる認証ページ用に /SignIn、/Register、およびすべての /Account/Login/* パスが予約されています。SPA のルートはこれらと競合してはなりません。サインインページには /login、登録ページには /registration を使用してください。

AUTH_PROVIDERS.length > 1 の場合は、必ず /login ページを作成してください。 プロバイダーが 1 つだけ設定されている場合(外部プロバイダーのみ、またはローカルプロバイダーのみ)、AuthButton の「Sign In」から login() を直接呼び出せるため、Login ページは必須ではありません。ただし、認証エラー、パスワードリセットリンク、招待バナーなどを表示する安定した場所となるため、作成することをおすすめします。

Login ページには、次の要件があります。

  • authService から AUTH_PROVIDERS、LOCAL_PROVIDER、EXTERNAL_PROVIDERS、loginWithProvider をインポートします
  • すべての外部プロバイダーをボタンとして表示します(EXTERNAL_PROVIDERS をループ)。各ボタンから loginWithProvider(provider, { returnUrl, invitationCode }) を呼び出します
  • LOCAL_PROVIDER がある場合は、ローカルのメールアドレス/パスワードフォームを表示します。送信時に loginWithProvider(LOCAL_PROVIDER, { returnUrl, invitationCode, credentials: { credential, password, rememberMe } }) を呼び出します
  • LOCAL_PROVIDER.loginByEmail に応じて認証情報フィールドを切り替えます。true の場合は Email(type email)、false の場合は Username(type text)を使用します
  • 送信処理中はすべてのボタンを無効にします(ローカル用に isSubmitting フラグ、外部用に externalSubmittingId を使用)
  • loginWithProvider からの TermsRequiredError を捕捉し、/terms に遷移します
  • URL に invitationCode が含まれる場合は、次の招待バナーを表示します:「招待 {code} を利用するにはサインインしてください。サインイン後、招待がアカウントに紐付けられます。」
  • ?message= クエリパラメーターから解析したサーバー側認証エラー(getAuthError() を使用)と、?sessionExpired=true によるセッション有効期限切れメッセージ(getSessionExpiredMessage() を使用)を表示します
  • LOCAL_PROVIDER があり、ResetPasswordEnabled が true の場合は、/forgot-password(SPA ルート)への「パスワードをお忘れですか?」リンクを含めます
  • REGISTRATION_MODE が Open registration only または Both の場合は、/registration への「アカウントを作成」リンクを含めます(Invitation-only および Registration disabled の場合は省略します)

Phase 2.1 の LOGIN_LAYOUT に基づいてレイアウトを表示します。

LOGIN_LAYOUTレイアウト構成
horizontal-row(デフォルト)上部に外部プロバイダーを flex-wrap の行で配置し、「OR SIGN IN WITH EMAIL」の区切り線、その下にローカルフォームを配置します。各外部ボタンには flex: 1 1 0; min-width: 0 を指定し、長いラベルは省略記号で切り詰めます。
vertical-stack外部プロバイダーを縦に全幅で積み重ね、「OR SIGN IN WITH EMAIL」の区切り線、その下にローカルフォームを配置します。各外部ボタンの幅はカード全体に合わせます。
primary-spotlightPRIMARY_PROVIDER_ID に一致するプロバイダーを、大きなプライマリ CTA として表示します。その他の外部プロバイダーは、「More sign-in options」または「Other ways to sign in」というラベルを付けた <details> の開示セクション内に配置します。その後に区切り線とローカルフォームを配置します。
tabbedプロバイダーごとに 1 つ(displayName)のタブを持つタブバーを表示します。選択したタブの UI をその下に表示します。外部プロバイダーの場合、タブのコンテンツは「Sign in with X」ボタンのみです。ローカルの場合は、メールアドレス/パスワードフォームを表示します。

React での各レイアウトパターンの完全なコード例については、authentication-reference.md を参照してください。

/login ページが存在する場合、AuthButton の「Sign In」からログイン関数を直接呼び出すのではなく、<Link to="/login"> を使用して /login に遷移する必要があります。

5.1.2 登録ページを作成する

REGISTRATION_MODE が Registration disabled でない場合は、必ず /registration ページを作成してください — モードが Open registration only、Invitation-only、Both のいずれであるか、またローカルプロバイダー、外部プロバイダー、またはその両方が設定されているかにかかわらず作成します。ローカルプロバイダーと外部プロバイダーの両方がある場合、ページのレイアウトは Login ページと同じです。上部に外部プロバイダーのボタンを横一列に並べ、その下に「or sign up with email」の区切り線を置き、さらに下にローカル登録フォームを配置します(ローカル認証が設定されている場合)。招待のみのモードでは、/redeem-invitation フロー(フェーズ 5.1.7)またはサーバー経由のフロー(Code-Site-Shell-Header が /account/login/register を捕捉)からページにアクセスします。直接のナビゲーションではありませんが、React Router がレンダリングできるよう、SPA のルートは必ず作成してください。

重要なアーキテクチャ上の注意: サーバー側の登録ページ(/Account/Login/Register)は ASP.NET Web Forms ページであり、MVC アクションではありません。authService のローカル register() 関数は、まずサーバーページを取得(GET)して ViewState とコントロール名を解析し、その後、正しいペイロードで POST することでこれに対応します。この処理は、ユーザーがローカル登録フォームを送信する場合にのみ適用されます。外部プロバイダーのボタンはこの処理をすべてスキップし、loginExternal() を呼び出します。

外部プロバイダーのボタン(EXTERNAL_PROVIDERS.length > 0 の場合):

外部プロバイダーの登録ボタンをページ上部に横一列で表示します。Login ページと同じレイアウトパターンを使用します(フェーズ 5.1.1)。各ボタンは authService の loginExternal(providerIdentifier, returnUrl, invitationCode) を呼び出します。ユーザーが「Sign up with Entra External ID」をクリックすると、SPA は招待コード(存在する場合)を URL に含めて外部認証を開始します。その後、サーバーの ExternalLoginCallback は、AllowContactMappingWithEmail による自動リンク、SPA 化された ExternalLoginConfirmation ページ(フェーズ 5.1.8)の表示、または招待コードが指定されている場合の招待の引き換えのいずれかを行います。これは Login ページの外部ボタンと同じコードパスです — ユーザーが /login と /registration のどちらからアクセスしたかにかかわらず、loginExternal() の動作は変わりません。連絡先がまだ存在しない場合、サーバーは初回サインイン時に連絡先を作成します。

EXTERNAL_PROVIDERS.length > 0 かつ LOCAL_PROVIDER が存在する場合は、外部プロバイダーのボタンとローカルフォームの間に区切り線(「or sign up with email」)を表示します。外部プロバイダーのみの場合(ローカルプロバイダーがない場合)は、区切り線もローカルフォームも表示せず、外部プロバイダーのボタンのみを表示します。つまり、このページは実質的に「登録に使用するプロバイダーを選択する」ページになります。

ローカルフォーム(LOCAL_PROVIDER が存在する場合):

ローカル登録フォームは、次の要件を満たす必要があります。

  • authService の register() 関数を呼び出します。この関数は、Web Forms の ViewState パターン(サーバーページを取得 → 解析 → 正しいコントロール名で POST)を処理します
  • Phase 2.1 の LocalLoginByEmail の選択に基づいて、適切な認証情報フィールドを表示します:
    • メールアドレスモード(LocalLoginByEmail = true):Email フィールド(type email)を表示します。これはログイン識別子とメールアドレスの両方として使用されます。
    • ユーザー名モード(LocalLoginByEmail = false):Username フィールド(type text)と、別の Email フィールド(type email)を表示します。どちらも必須です。Username はログイン識別子として、Email は連絡先レコード用として必要です。
  • Password と Confirm Password フィールド(どちらも type password)を含めます
  • 送信前に、クライアント側でパスワードが一致することを検証します
  • ?message= クエリパラメーターから解析したサーバー側の登録エラーを表示します(getAuthError() 経由)
  • URL クエリ文字列から invitationCode を解析して渡します(?invitationCode=... 経由でユーザーがアクセスする招待ベースの登録フロー用)
  • /login に戻る「すでにアカウントをお持ちですか?サインイン」リンクを含めます
  • 開発モードでは認証リダイレクトをスキップします — 開発モードではモックユーザーが常に「認証済み」となるため、登録フォームのテストが妨げられます。const isDev = window.location.hostname === 'localhost' を追加し、isAuthenticated && !isDev の場合にのみリダイレクトします。
  • サイト既存のサインインページのデザイン(中央配置のカードレイアウト)に合わせてスタイルを設定します

レイアウト — Login ページ(Phase 5.1.1)に合わせます。ユーザーが Phase 2.1 で LOGIN_LAYOUT を選択した場合、そのレイアウトの選択(横並び/縦積み/メイン項目の強調表示/タブ形式)は /login と /registration の両方に適用され、2 つのページで一貫したエクスペリエンスが得られます。

招待からメールアドレスを事前入力する場合(InvitationEnabled が true のとき。つまり、招待のみ、または両方のモード):

ユーザーが /registration?invitationCode=X にアクセスしたとき、招待された連絡先のメールアドレスをメールアドレスフィールドに事前入力します(サーバーでレンダリングされたページの動作に合わせます)。マウント時に fetchInvitationDetails(invitationCode) を呼び出して実装します:

useEffect(() => {
  if (!invitationCode) return
  fetchInvitationDetails(invitationCode).then(details => {
    if (details.email) setEmail(details.email)
  }).catch(() => { /* silent — user can enter email manually */ })
}, [invitationCode])

fetchInvitationDetails() 関数(authService.ts 内)は /Account/Login/Register?invitationCode={code} に GET リクエストを送信し、レンダリングされた HTML の #EmailTextBox 入力要素の value 属性からメールアドレスを解析します。実装方法については、authentication-reference.md を参照してください。

メールアドレス入力は制御コンポーネント(value={email})にし、編集できるようにします。必要に応じてユーザーが変更できます(サーバーの動作に合わせます)。

フレームワークごとの実装:

  • React:src/pages/Registration.tsx を作成し、ルーターに <Route path="/registration" element={<Registration />} /> を追加します。実装パターンについては、authentication-reference.md の RegisterForm コンポーネントを参照してください。サイト既存のスタイル設定パターン(インラインスタイル、CSS 変数など)に合わせて調整します
  • Vue:src/pages/Registration.vue を作成し、src/router/index.ts にルートを追加します
  • Angular:src/app/pages/registration/registration.component.ts を作成し、ルーター設定にルートを追加します
  • Astro:src/pages/registration.astro を作成します

REGISTRATION_MODE が Registration disabled の場合、/registration ページは作成しません。このページにユーザーが遷移するフローはありません。

5.1.3 パスワードを忘れた場合のページを作成する(ローカル認証のみ)

ローカル認証が構成されており、かつ ResetPasswordEnabled が true の場合、/forgot-password ページを作成します。これはユーザーのメールアドレスを収集し、サーバーに POST するシンプルなフォームで、サーバーはパスワードリセットリンクをメールで送信します。

Note: パスワードを忘れた場合のエンドポイント(/Account/Login/ForgotPassword)は、Web Forms ページ(登録など)ではなく、MVC フォーム(ログインなど)です。Email + __RequestVerificationToken を使用したシンプルな fetch() POST で動作します。authService の forgotPassword() 関数がこれを処理します。

パスワードを忘れた場合のページは、次の要件を満たす必要があります。

  • メールアドレス入力フィールドを表示する
  • 送信時に authService の forgotPassword(email) を呼び出す
  • 成功とエラーの両方を処理する: 成功時には .then() を使用し(緑色のチェックマークとともに「Check your email」確認を表示し、フォームを非表示にする)、エラー時には .catch() を使用します(エラーをインラインで表示し、ボタンをリセットする)。.catch() だけを処理しないでください。.then() が処理されていないと、ボタンが「Sending...」状態のまま固まってしまいます。
  • emailSent 状態を追跡する — true の場合、フォームを成功メッセージに置き換えます: 「We've sent a password reset link to your email address. Please check your inbox and follow the instructions.」と「Back to sign in」リンクを表示します
  • サーバーエラーをインラインで表示する(forgotPassword() 関数は fetch を使用し、解析済みのエラーをスローします)
  • /login への「Back to sign in」リンクを含める
  • ログインおよび登録と同じ validate-on-blur パターンを使用する(ブラー時にメール形式を検証し、変更時にクリアする)

ログインページの「Forgot password?」リンクは、/Account/Login/ForgotPassword(サーバー URL)ではなく、/forgot-password(SPA ルート)を指す必要があります。

サーバーがリクエストを処理した後、リセットメールが送信されます。メール内のリセットリンクは、サーバーの /Account/Login/ResetPassword?UserId=...&Code=... を指しますが、これは Header テンプレートのリダイレクトスクリプト(フェーズ 5.1.6 を参照)によってインターセプトされ、SPA の /reset-password ページにリダイレクトされます。

5.1.4 すべての認証ページの検証パターン(ローカル認証のみ)

すべてのローカル認証ページ(ログイン、登録、パスワードを忘れた場合)は、リアルタイムのフィールド検証のために validate-on-blur、clear-on-change を実装する必要があります。これはモダンな UX パターンです — ユーザーがフィールドから離れたときにエラーが表示され、修正するとすぐに消えます。

実装パターン:

  1. フィールドごとに touched 状態を追跡する(ユーザーがどのフィールドを操作したか)
  2. ブラー時(onBlur): フィールドを touched としてマークし、検証を実行し、エラーを即座に表示する
  3. 変更時(onChange): フィールドがすでに touched である場合、再検証し、値が有効になった時点でエラーをクリアする。また、変更があればサーバーエラーもクリアする。
  4. 送信時: すべてのフィールドを touched としてマークし、すべてを検証し、すべてのエラーを一度に表示する
  5. showError(field) ヘルパー: フィールドが touched である場合にのみエラーを返す

検証ルール:

ページフィールド検証
ログインメールアドレス必須 + 有効なメール形式
ログインパスワード必須
登録メールアドレス必須 + 有効なメール形式
登録パスワード必須 + 8 文字以上 + 4 つのカテゴリ(小文字、大文字、数字、特殊文字)のうち少なくとも 3 つからの文字
登録パスワードの確認必須 + パスワードと一致する必要がある
パスワードを忘れた場合メールアドレス必須 + 有効なメール形式

パスワード強度の検証は、デフォルトの Power Pages パスワードポリシー(EnforcePasswordPolicy)に一致します。サイト作成者が Authentication/UserManager/PasswordValidator/* サイト設定を通じてパスワードポリシーをカスタマイズした場合、クライアント側の検証もそれに一致させる必要があります。

5.1.5 利用規約ページを作成する(利用規約が有効な場合)

フェーズ 2 でユーザーが利用規約を有効にした場合、/terms SPA ページを作成します。このページは、ローカルサインイン、ローカル登録、外部プロバイダー(Entra External ID、OIDC、ソーシャル)のすべての認証フローに対応します。そのために、次の 2 つの補完的な仕組みを使います。

  1. ローカル認証フロー(loginLocal、register):SPA のページ遷移を維持するために fetch() を使用します。認証サービスは response.url からサーバーの TermsAndConditions へのリダイレクトを検出し、TermsRequiredError をスローします。ログイン/登録ページはこのエラーを捕捉し、/terms に遷移します。
  2. 外部認証フロー(loginExternal、loginWithProvider の外部ブランチ):IdP との往復中にブラウザーが SPA を離れるように、form.submit() を使用します。IdP のコールバック後、サーバーは /Account/Login/TermsAndConditions?ReturnUrl=/&UseExternalSignInAsync=True&IsFacebook=False&IsInternalAADUser=False にリダイレクトする場合があります。Code-Site-Shell-Header スクリプト(フェーズ 5.1.6)はこの URL を捕捉し、クエリ文字列を保持したまま SPA の /terms ルートにリダイレクトします。これにより、acceptTerms() はサーバーが想定するサインイン完了パスを認識できます。

利用規約ページには、次の内容が必要です。

  • フェーズ 2 で収集した 4 つのスニペット値をコンポーネントの先頭で定数としてハードコードします。
    const TERMS_HEADING = '<value from Phase 2 or default>'
    const TERMS_CONTENT = '<HTML content from Phase 2 or default>'
    const TERMS_AGREEMENT_TEXT = '<value from Phase 2 or default>'
    const TERMS_BUTTON_TEXT = '<value from Phase 2 or default>'
    
  • 見出し、利用規約の内容(dangerouslySetInnerHTML を使って HTML としてレンダリング)、同意文付きのチェックボックス、確認ボタンを表示します。
  • 確認ボタンは authService の acceptTerms('/') を呼び出します。これにより、window.location.search からクエリ文字列を読み取り、サーバーの利用規約ページを(同じクエリ文字列を保持したまま)取得して偽造防止トークンを取得した後、クエリ文字列のフラグをリクエスト本文に含めて、同じ URL に同意を POST します。
  • チェックボックスがオンになるまで、確認ボタンを無効にします。
  • acceptTerms() がエラーをスローした場合は、サーバーエラーをインラインで表示します。
  • /login への「サインインに戻る」リンクを含めます。

ログインページと登録ページでは、TermsRequiredError(ローカル認証パス)を捕捉する必要があります。

  • ログインページの loginLocal() の catch ブロックで、err instanceof TermsRequiredError の場合は /terms に遷移します。
  • 登録ページの register() の catch ブロックで、err instanceof TermsRequiredError の場合は /terms に遷移します。

**外部認証パスの loginExternal では、SPA コードの変更は不要です。**ヘッダーテンプレートのリダイレクト(フェーズ 5.1.6)が透過的に処理します。外部ユーザーは IdP との往復後に /terms?ReturnUrl=/&UseExternalSignInAsync=True&... に到着し、SPA が利用規約ページをレンダリングします。acceptTerms() はクエリ文字列を使い、正しい UseExternalSignInAsync/IsFacebook/IsInternalAADUser フラグを付けて POST します。

サーバーが利用規約を表示する仕組み(参考):

  • ローカルログインフロー:認証後、サーバーは /Account/Login/TermsAndConditions にリダイレクトします。loginLocal() 内の response.url.includes('TermsAndConditions') で捕捉します。
  • ローカル登録フロー:登録後、サーバーは /TermsAndConditions?ReturnUrl=%2F にリダイレクトします。register() 内の response.url.includes('TermsAndConditions') で捕捉します。
  • 外部ログインフロー:サーバーは /Account/Login/ExternalLoginCallback から /Account/Login/TermsAndConditions?ReturnUrl=/&UseExternalSignInAsync=True&IsFacebook=False&IsInternalAADUser=False にリダイレクトします。ヘッダーテンプレートのリダイレクトで捕捉します。

acceptTerms() はクエリ文字列を考慮する必要があります(利用規約と併せて外部プロバイダーを設定する場合に必須です)。

export async function acceptTerms(returnUrl?: string): Promise<void> {
  // Parse the flags from window.location.search — set by the server's redirect URL.
  // For local-auth users (no query string), defaults apply.
  const params = new URLSearchParams(window.location.search);
  const useExternalSignInAsync = params.get('UseExternalSignInAsync') || 'False';
  const isFacebook = params.get('IsFacebook') || 'False';
  const isInternalAADUser = params.get('IsInternalAADUser') || 'False';

  // Fetch the server terms page WITH the original query string preserved
  const serverTermsUrl = `/Account/Login/TermsAndConditions${window.location.search}`;
  const pageResponse = await fetch(serverTermsUrl, { credentials: 'same-origin', redirect: 'follow' });

  // ... extract anti-forgery token from rendered HTML ...

  // POST back to the same URL with body flags matching the query-string flags.
  // DO NOT hardcode UseExternalSignInAsync=False — external users need True.
  const body = new URLSearchParams();
  body.set('__RequestVerificationToken', antiForgeryToken);
  body.set('IsTermsAndConditionsAccepted', 'true');
  body.set('UseExternalSignInAsync', useExternalSignInAsync);
  body.set('IsFacebook', isFacebook);
  body.set('IsInternalAADUser', isInternalAADUser);
  body.set('InvitationCode', '');
  // ... POST and handle response ...
}

実装の全容については、authentication-reference.md を参照してください。

フレームワーク固有の実装:

  • React:src/pages/Terms.tsx を作成し、ルーターに <Route path="/terms" element={<Terms />} /> を追加します。

コンテンツの更新:サイト作成者が利用規約の文章を変更する場合は、Terms ページコンポーネントの定数を更新して再デプロイします。Dataverse のコンテンツスニペット(Account/Signin/TermsAndConditionsCopy など)も一致するように更新する必要があります。サーバーでレンダリングされる利用規約ページはスニペットを読み込み、SPA はハードコードされた定数を読み込むため、両方を同期させてください。

5.1.6 パスワードリセットページの作成とヘッダーテンプレートによるリダイレクト(ローカル認証のみ)

ローカル認証が設定されており、かつ ResetPasswordEnabled が true の場合、完全な SPA のパスワードリセット機能を作成します。これには次の 2 つの要素があります。

1. Code-Site-Shell-Header テンプレート

パスワードリセットメールでは、ユーザーをサーバーレンダリングページである /Account/Login/ResetPassword?UserId=...&Code=... に誘導します。ユーザーを SPA 内にとどめるには、サーバーレンダリングページで実行されるクライアント側のリダイレクトスクリプトが必要です。

新しいテンプレートが必要な理由 pac pages upload-code-site コマンドは、アップロードのたびに元の「Header」および「Footer」Web テンプレートのコンテンツを意図的に <div/> に置き換えます。Header テンプレートに追加したスクリプトはすべて消去されます。この問題を回避するには、別の Web テンプレート(Code-Site-Shell-Header)を作成し、ウェブサイトレコードでそのテンプレートを指定します。

.powerpages-site/web-templates/code-site-shell-header/ に新しい Web テンプレートを作成します。

Code-Site-Shell-Header.webtemplate.yml:

id: <generate-a-new-uuid>
name: Code-Site-Shell-Header

Code-Site-Shell-Header.webtemplate.source.html:

<div/>
<script>
  // SPA Site Shell Header — Server-to-SPA redirect for auth pages.
  // This template runs on server-rendered pages and redirects to SPA equivalents.
  // Uses a separate template because pac pages upload-code-site wipes the original Header.
  (function () {
    var path = window.location.pathname.toLowerCase();
    var search = window.location.search;
    var spaBase = window.location.origin;
    // Add an entry here for each server-rendered auth page that has a SPA equivalent.
    // Only include entries the site actually needs (e.g., omit /redeeminvitation when
    // InvitationEnabled is false).
    var redirects = {
      '/account/login/resetpassword': '/reset-password',
      '/account/login/redeeminvitation': '/redeem-invitation',
      '/register': '/redeem-invitation',
      '/account/login/register': '/registration',
      '/account/login/externallogincallback': '/external-login-confirmation',
      '/account/login/termsandconditions': '/terms',
      '/account/login/externalauthenticationfailed': '/login',
      '/signin': '/login'
    };
    // Special case: ExternalAuthenticationFailed may arrive with no query string
    // (generic failure) or with ?message=access_denied (user-denied at IdP).
    // Ensure the Login page always shows SOME error indication.
    if (path === '/account/login/externalauthenticationfailed' && !search) {
      window.location.replace(spaBase + '/login?message=external_auth_failed');
      return;
    }
    for (var serverPath in redirects) {
      if (path === serverPath) {
        window.location.replace(spaBase + redirects[serverPath] + search);
        return;
      }
    }
  })();
</script>

条件付きエントリ — SPA に存在するページのリダイレクトエントリのみを含めます。

リダイレクト含める条件
'/account/login/resetpassword': '/reset-password'ローカル認証を使用し、ResetPasswordEnabled = true の場合(フェーズ 5.1.6)
'/account/login/redeeminvitation': '/redeem-invitation'REGISTRATION_MODE が Invitation-only または Both の場合(フェーズ 5.1.7)
'/register': '/redeem-invitation'REGISTRATION_MODE が Invitation-only または Both の場合(フェーズ 5.1.7)。/Register は LoginController.RedeemInvitation のエイリアスルートです。RegistrationManager.cs のゲーティングロジックに従い、連絡先も招待コンテキストもない外部ユーザーは、ExternalLoginCallback からサーバーによってこのページにリダイレクトされます。このルートを捕捉することで、招待リンクなしでサインインを開始した外部ユーザーにも SPA の UX を維持できます。
'/account/login/register': '/registration'REGISTRATION_MODE が Registration disabled でない場合(フェーズ 5.1.2)。これはローカルの Register Web Forms ページです。RedeemInvitation フォームの送信後、サーバーはこのページにリダイレクトします(/Register → /Account/Login/Register?invitationCode=...)。このルートを捕捉することで、ユーザーは SPA の /registration ページにとどまれます。このページは Login ページのレイアウトをそのまま反映しており、区切り線の上に外部プロバイダーボタン、下にローカルフォームが配置されているため、ユーザーはどちらの方法でもサインアップを完了できます(フェーズ 5.1.2)。
'/account/login/externallogincallback': '/external-login-confirmation'外部プロバイダーが設定されている場合(フェーズ 5.1.8)。初回の外部サインインを SPA に取り込みます
'/account/login/termsandconditions': '/terms'利用規約が有効な場合(あらゆる認証フロー。フェーズ 5.1.5 を参照)。外部プロバイダーの場合、認証後にサーバーが利用規約へリダイレクトする動作を捕捉します
'/account/login/externalauthenticationfailed': '/login'外部プロバイダーが設定されている場合。外部認証に失敗すると、サーバーはこのページにリダイレクトします(無効なトークン、発行者の不一致、IdP でのユーザーによる拒否、IdP の停止など)。このパスは OWIN のスタートアップでハードコードされており、サイト設定から変更できません(authentication-reference.md を参照)。リダイレクトスクリプトでは、この URL を特別に処理します。サーバーが ?message=access_denied を追加した場合(ユーザーによる拒否)、その値を引き継ぎ、getAuthError() に「アクセスが拒否されました。」と表示します。サーバーがクエリ文字列を追加しない場合(一般的な失敗)、スクリプトが ?message=external_auth_failed を挿入し、Login ページに「外部プロバイダーでのサインインに失敗しました。もう一度お試しください。」と表示します(このコードを authService の AUTH_ERROR_MESSAGES に追加してください)。
'/signin': '/login'常に含めてください(あらゆる認証フロー)。/SignIn はサーバーの従来のサインインページです。保護されたサーバーレンダリングのパス(例:/profile、/Account/Manage、Web ロールで保護されたサーバーページ)に未認証ユーザーがアクセスすると、サーバーは常にこのページにリダイレクトします。このエントリーがないと、ユーザーは SPA からサーバーレンダリングのサインイン UI に移動してしまいます。サーバーが追加する ?ReturnUrl=... クエリパラメーターは変更せずに引き継がれます。SPA の /login ページは現在これを無視し(サインイン後は常に / に戻ります)、これは許容できる動作です。

2 つの新しいエントリー(/register と /account/login/register)について: これらは、ほかの方法では見つけにくい特定の外部認証フローに対応します。外部ユーザー(Entra External ID、OIDC、SAML2、ソーシャル認証)が招待メールのリンクを先にクリックせずに「サインイン」ボタンをクリックし、Dataverse に既存の連絡先がない場合、サーバーはサーバー側でレンダリングされる招待フローに誘導します。

  1. POST /Account/Login/ExternalLogin → IdP → /signin-{ProviderName-lowercased} → /Account/Login/ExternalLoginCallback
  2. ExternalLoginCallback は連絡先も招待も見つからないため、/Register?ReturnUrl=/(サーバー側でレンダリングされる RedeemInvitation フォーム)にリダイレクトします
  3. ユーザーが招待コードを入力 → サーバーが検証 → /Account/Login/Register?invitationCode=...(外部プロバイダーボタンがレンダリングされるローカルの Register Web Forms ページ)にリダイレクトします
  4. ユーザーが Register ページの外部プロバイダーボタンをクリック → /Account/Login/ExternalLogin?InvitationCode=... → URL に招待情報を含む外部認証の 2 回目の実行 → 連絡先が作成され、招待が引き換えられます

/register と /account/login/register のリダイレクトがない場合、ユーザーにはサーバー側でレンダリングされる RedeemInvitation フォームと Web Forms の Register ページが表示され、どちらも SPA の UX を損ないます。これらのリダイレクトは Entra External ID だけでなく、すべての外部プロバイダーに適用されます。サーバーのリダイレクト処理はプロバイダーに依存しないためです。

次に、website.yml を更新して、headerwebtemplateid が新しいテンプレートの ID を指すようにします。

headerwebtemplateid: <new-template-uuid>

元の「Header」テンプレートは <div/> のままにします(アップロードコマンドによって引き続き上書きされますが、問題ありません)。Code-Site-Shell-Header テンプレートは、「Header」と「Footer」という名前のテンプレートのみを対象とするコマンドの影響を受けないため、アップロード後も残ります。

この方法は拡張可能です。追加のサーバーから SPA へのリダイレクトを redirects オブジェクトに追加できます(例:メール確認ページ)。

2. SPA のパスワードリセットページ

/reset-password ページを作成します。このページでは、次の処理を行います。

  • URL のクエリパラメーターから UserId と Code を読み取ります(ヘッダーのリダイレクトによって保持されます)
  • いずれかのパラメーターがない場合は、「Invalid Reset Link」と /forgot-password へのリンクを表示します
  • 新しいパスワードとパスワード確認の入力欄を表示し、フォーカスが外れたときに検証します(パスワード強度の検証は登録時と同じです)
  • 送信時に authService の resetPassword(userId, code, password, confirmPassword) を呼び出します
  • 成功時は /login?message=password_reset_success にリダイレクトします
  • エラー時はサーバーエラーをインラインで表示します

resetPassword() 関数は、/Account/Login/ResetPassword に対する MVC フォーム POST(ViewState なし)で、次のフィールドを送信します:__RequestVerificationToken、UserId、Code、Password、ConfirmPassword。注:ここでのパスワードフィールドは Password です(ログイン時の PasswordValue ではありません。エンドポイントによってフィールド名が異なります)。

ログインページで成功メッセージを処理する必要があります:マウント時に URL に ?message=password_reset_success が含まれているか確認し、緑色の成功バナー「パスワードがリセットされました。新しいパスワードでサインインしてください。」を表示します。

フレームワーク別の実装:

  • React:src/pages/ResetPassword.tsx を作成し、ルーターに <Route path="/reset-password" element={<ResetPassword />} /> を追加します
5.1.7 招待の引き換えページを作成する(招待モードのみ)

REGISTRATION_MODE が Invitation-only または Both の場合、/redeem-invitation SPA ページを作成します。これは、招待メールのリンクをクリックしたユーザーが最初にアクセスするページです。リンクの形式は {site-url}/Account/Login/RedeemInvitation?invitation={code} です。Code-Site-Shell-Header のリダイレクト(フェーズ 5.1.6)がこのリンクを検出し、SPA のルートに転送します。

ユーザーが /redeem-invitation にアクセスする経路は 2 つあります:

  1. 招待リンクからの直接アクセス:ユーザーがメールのリンク /Account/Login/RedeemInvitation?invitation={code} をクリック → ヘッダーのスクリプトが /redeem-invitation?invitation={code} にリダイレクトします。最初から URL に招待コードを含む、シンプルな SPA のエントリーポイントです。
  2. サーバー経由のリダイレクトパス(外部認証、連絡先なし、招待なし):ユーザーは招待リンクをクリックせずに、ログインページで「Sign in with [external provider]」をクリックします。外部認証を完了すると、サーバーの ExternalLoginCallback は連絡先も招待も見つからず、/Register?ReturnUrl=...(LoginController.RedeemInvitation のエイリアス)にリダイレクトします。ヘッダースクリプトが /register を検出して /redeem-invitation にリダイレクトします。同じ SPA ページが表示されますが、招待コードは事前入力されていないため、ユーザーが入力します。このパスは Entra External ID だけでなく、すべての外部プロバイダーに適用されます。サーバー側のリダイレクト処理はプロバイダーに依存しません。Entra External ID と Terms を使用する稼働中のサイトで、HAR の分析により確認済みです。

置き換え対象のサーバーフロー: サーバーの LoginController.RedeemInvitation アクション(crm.solutions.portal/Samples/MasterPortal/Areas/Account/Controllers/LoginController.cs の 3232~3310 行目)は、招待コードを検証して次のように分岐します。

  • RedeemByLogin = false(新規ユーザー)→ 302 で /Account/Login/Register?invitationCode={code} にリダイレクトします。サーバーは招待コードを使った登録を想定しています
  • RedeemByLogin = true(既存ユーザー)→ Login ビューを返す 200 OK。招待コードはフォームの action URL に埋め込まれています。サーバーはサインインを想定し、認証後に RedirectOnPostAuthenticate で招待を引き換えます
  • コードが無効/期限切れ/引き換え済み → フォームを再表示する 200 OK。エラーは #redeemInvitation-validation-summary に表示されます(3 つの状態はいずれも同じ Invalid_Invitation_Code_Exception として通知されます)

SPA の設計 — サーバーレンダリングページを完全に置き換えます:

SPA ページは authService の redeemInvitation(code, redeemByLogin) を呼び出します。この関数は redirect: 'manual' を指定した fetch() を使い、サーバーの 302 リダイレクトを捕捉します。レスポンスに応じて、次のように処理します。

  • response.type === 'opaqueredirect' → サーバーがコードを検証し、登録ページにリダイレクトしようとしたことを示します → SPA は /registration?invitationCode={code}(Phase 5.1.2 で作成済みのページ)に遷移します
  • Login フォームのマーカーを含む HTML が返る 200 OK → ユーザーはログインフローを選択しています → SPA は /login?invitationCode={code}(既存のページ。後述のとおり更新が必要)に遷移します
  • 検証サマリーを含む HTML が返る 200 OK → コードが無効です → 解析したサーバーエラーをスローします(既存の parseServerErrors() ヘルパーを使用)

DevTools に表示される挙動:POST の後、ブラウザーのネットワークパネルには、302 の Location の遷移先(例:/Account/Login/Register)が中断されたリクエスト(net::ERR_ABORTED)として表示されます。これは想定どおりです。redirect: 'manual' を使って、意図的にこのリダイレクトを追跡しないようにしています。このフローでは response.type === 'opaqueredirect' を使い、実際にリダイレクトをたどることなく、リダイレクトが発生したことを検出します。

招待引き換えページでは、次の要件を満たす必要があります。

  • URL クエリから invitation または InvitationCode を読み取ります(メールではどちらの大文字・小文字も使われる可能性があるため、両方に対応します)
  • 招待コード入力欄にコードを事前入力しますが、編集可能な状態にします(メールリンクなしでユーザーが手入力する場合に備えます)
  • チェックボックスを表示します:「登録せずに既存のアカウントでサインインする」(この設定が RedeemByLogin を制御します)
  • フォーカスが外れたときに検証します。招待コードは必須で、空にできません
  • 送信時に redeemInvitation() を呼び出し、返された nextStep に応じて遷移します
  • サーバーエラーをインラインで表示します(既存の parseServerErrors で解析します)
  • /login への「サインインに戻る」リンクを設けます
  • 他の認証ページに合わせたスタイルにします

ログインページの更新(必須):

招待モードが有効な場合、ログインページ(Phase 5.1.1)も更新する必要があります。

  1. マウント時に URL クエリパラメーターから invitationCode を読み取ります
  2. 値がある場合は、情報バナーを表示します:「招待 {code} を引き換えるにはサインインしてください。サインイン後、招待がアカウントに紐付けられます。」
  3. invitationCode を新しい 5 番目のパラメーターとして loginLocal() に渡します。認証サービスは /SignIn POST の URL に ?InvitationCode={code} を付加します。サーバーの Login(model, returnUrl, invitationCode) ハンドラーはこの値を読み取り、認証成功後に RedirectOnPostAuthenticate で招待を引き換えます。 フレームワーク固有の実装:
  • React:src/pages/RedeemInvitation.tsx を作成し、ルーターに <Route path="/redeem-invitation" element={<RedeemInvitation />} /> を追加します。実装パターンについては、authentication-reference.md の RedeemInvitation コンポーネントを参照してください。
  • Vue / Angular / Astro:各フレームワークの慣例に沿って、React のパターンを適用します。

認証サービスへの追加(招待モードが有効な場合に必須):

  • redeemInvitation(invitationCode, redeemByLogin, returnUrl) — 関数の一覧についてはフェーズ 3.2 を参照してください。コード全体は authentication-reference.md にあります。
  • fetchInvitationDetails(invitationCode) — メールアドレスの事前入力については、フェーズ 5.1.2 で説明済みです。
  • loginLocal() を更新して、オプションの invitationCode パラメーター(第 5 引数)を受け取れるようにします — /SignIn URL に ?InvitationCode={code} を追加します。
5.1.8 外部ログイン確認ページの作成(外部プロバイダーのみ)

外部プロバイダー(OIDC、Entra External ID、SAML2、WS-Federation、ソーシャル)のいずれかが設定されている場合は、/external-login-confirmation SPA ページを作成します。このページは、サーバーが本来 ExternalLoginConfirmation.aspx を表示する初回の外部サインインフローを処理します。

このページで置き換えるサーバーフロー: ユーザーが初めて外部サインインし、Dataverse の連絡先が存在しない場合、サーバーの LoginController.ExternalLoginCallback アクション(crm.solutions.portal/Samples/MasterPortal/Areas/Account/Controllers/LoginController.cs の約 761 行目)は、コールバック URL(/Account/Login/ExternalLoginCallback)で ExternalLoginConfirmation ビューを表示します。このビューには編集可能なメールアドレスフィールドと、クレームから取得した非表示の firstName/lastName/username があり、/Account/Login/ExternalLoginConfirmation に POST します。

SPA 化できる理由(Reset Password と Redeem Invitation と同じパターン):

  • __External クッキー(有効期間 5 分、AuthenticationMode = Passive、Secure = Always)は、IdP コールバックとフォーム POST の間のクレームを保存します。same-origin の fetch では自動的に送信されます。
  • SPA はサーバー URL をフェッチし、レンダリングされた HTML を解析して事前入力値とアンチフォージェリトークンを抽出し、独自のフォームを表示してから POST で送信します。サーバーはフォームを変更せずに処理します。

スキップ条件 — 次の場合、サーバーはこのページを完全にスキップします。

  • ユーザーのメールクレームが既存の連絡先と一致し、かつ AllowContactMappingWithEmail = true → サーバーが自動的にサインインします。
  • ユーザーが招待コードを持ち、そのコードが ESS システム内の既存の連絡先に紐付いている → サーバーが自動的にサインインします。
  • 登録が無効になっているか、ユーザーに登録が許可されていない

このような場合、ユーザーは IdP コールバックの後、そのままホームに移動します — SPA ページはマウントされません。

リダイレクトチェーン
Email link / "Sign in with Entra External ID" button
   ↓
POST /Account/Login/ExternalLogin → server redirects to IdP
   ↓
User authenticates at IdP → IdP callback to /signin-{providername}
   ↓
OIDC middleware processes callback (sets __External cookie with claims)
   ↓
Forward to /Account/Login/ExternalLoginCallback
   ↓
ExternalLoginCallback action: new user, no existing contact
   → returns ExternalLoginConfirmation view at the callback URL
   ↓
┌─ Code-Site-Shell-Header script catches /account/login/externallogincallback ─┐
│  → window.location.replace('/external-login-confirmation' + query)            │
└────────────────────────────────────────────────────────────────────────────────┘
   ↓
SPA /external-login-confirmation page mounts
   → calls fetchExternalLoginDetails()
     → GET /Account/Login/ExternalLoginCallback (__External cookie auto-sent)
     → server returns the same rendered HTML
     → SPA parses #Email, #FirstName, #LastName, #Username, #InvitationCode,
       __RequestVerificationToken, and the form's action URL for ReturnUrl
   ↓
SPA renders own form, email pre-filled and editable
   ↓
User clicks "Create my account"
   → confirmExternalLogin() POST /Account/Login/ExternalLoginConfirmation
     (form fields + anti-forgery token, redirect:'manual')
   ↓
   ├── response.type === 'opaqueredirect' → window.location.href = returnUrl
   │     (server sets ApplicationCookie BEFORE returning 302 — user is signed in)
   ├── 200 OK with validation-summary → throw parsed error (e.g., duplicate email
   │     when RequireUniqueEmail is true and user typed an existing email)
   └── 200 OK with TermsAndConditions markers → throw TermsRequiredError →
         SPA navigates to /terms
認証サービスへの追加(外部プロバイダーが設定されている場合)

src/services/authService.ts に 3 つの要素を追加します — コード全体は authentication-reference.md にあります。

  • ExternalLoginCookieExpiredError クラス — __External クッキーの有効期限(5 分)を超過した場合にスローされます。このページは、期限切れのセッションを知らせるメッセージを付けて /login に移動します。
  • fetchExternalLoginDetails() — /Account/Login/ExternalLoginCallback をフェッチし、HTML を解析して事前入力値とアンチフォージェリトークンを取得します。フォームが存在しない場合は ExternalLoginCookieExpiredError をスローします。
  • confirmExternalLogin(details) — redirect:'manual' を指定して /Account/Login/ExternalLoginConfirmation に POST します。レスポンスの種類に応じて、移動するか例外をスローします。
SPA ページ

src/pages/ExternalLoginConfirmation.tsx を作成します。

  • マウント時に fetchExternalLoginDetails() を呼び出します。状態は「読み込み中」「Cookie の期限切れ」「準備完了」の 3 つです。
  • Cookie の期限が切れている場合は、「サインインセッションの有効期限が切れました」と、/login への「サインインに戻る」リンクを表示します。
  • 準備完了時は、ユーザーのフルネーム(クレームから取得、読み取り専用)、編集可能なメールアドレス入力欄(デフォルト値はメールアドレスのクレーム)、および invitationCode が空でない場合は「招待バナー」を表示します。
  • 送信時に confirmExternalLogin(details) を呼び出します。TermsRequiredError が発生した場合は /terms に遷移します。ExternalLoginCookieExpiredError(セッション途中での期限切れ)が発生した場合は、期限切れ状態に UI を切り替えます。
  • サーバー側の検証エラーは parseServerErrors でインライン表示します。
ルーティング

<Route path="/external-login-confirmation" element={<ExternalLoginConfirmation />} /> を追加します。

DevTools の動作

POST の後、ネットワークパネルに 302 の Location の遷移先(例:returnUrl のパス)が中断されたリクエスト(net::ERR_ABORTED)として表示される場合があります。これは RedeemInvitation のパターンと同じです。redirect:'manual' による想定どおりの動作であり、エラーではありません。

エッジケース
  • 2FA:ユーザーで 2FA が有効になっている場合、SignInAsync の完了後(302 リダイレクト中)にチャレンジが行われます。SPA 化されたフローは影響しません。2FA チャレンジ(SendCode/VerifyCode)は、それ自体がサーバーでレンダリングされるフローであり、インターセプトできません。
  • SameSite=Strict:__External Cookie が SameSite=Strict で構成されている場合、SPA の fetch は Cookie を含めないため、fetchExternalLoginDetails は直ちに ExternalLoginCookieExpiredError をスローします。デフォルトは Lax(SameSiteCookieHelper.GetOwinSameSiteFromSiteSettings で設定)であり、問題なく動作します。
  • 外部ログイン経由で招待されたユーザー:招待コードは fetchExternalLoginDetails によってフォームの action URL から取得され、POST で再送信されます。サーバーは連絡先の作成時に招待を引き換えます。
5.1.9 プロフィールページを作成する(INCLUDE_PROFILE_PAGE = true の場合)

INCLUDE_PROFILE_PAGE = true の場合に限ります(フェーズ 2.1)。それ以外の場合、このフェーズ全体をスキップします。

サインイン済みのユーザーが Power Pages Web API を通じて連絡先レコードを編集する /user-profile SPA ページを作成します。プロバイダーに依存せず、サインイン後の連絡先レコードを操作するため、ローカルプロバイダーとすべての外部プロバイダーで動作します。

⚠ 必須の命名規則 — 変更しないでください:

項目必須の名前避けるべき誤った例
SPA ルート/user-profile❌ /profile(サーバー予約済み)、/my-profile、/account/profile
ページコンポーネントファイルsrc/pages/UserProfile.tsx❌ Profile.tsx、MyProfile.tsx、UserProfilePage.tsx
コンポーネントのエクスポート名UserProfile(デフォルトエクスポート)❌ Profile、ProfilePage
サービス関数 — 読み取りgetMyProfile(contactId)❌ getOwnContact、getProfile、fetchProfile
サービス関数 — 書き込みupdateMyProfile(contactId, payload)❌ patchOwnContact、updateProfile、saveProfile
型 — 連絡先の形状ProfileContact❌ Contact、UserContact
型 — パッチペイロードProfileUpdate❌ ContactUpdate、ProfilePatch

命名が重要な理由:ファイル名とルート名は、このスキルの他の箇所(フェーズ 7.1 の検証、フェーズ 8.3 の概要)、auth-reference.md、および評価シナリオで参照されます。名前を変更すると、相互参照が連鎖的に壊れます。ルート /profile は特に使用してはなりません。Power Pages サーバーが、従来のサーバーレンダリングされたプロフィールページ用に予約しているためです(Authentication/Registration/ProfileRedirectEnabled はデフォルトでサインイン後にユーザーを /profile にリダイレクトします。このリダイレクトを防ぐため false に設定していますが、同じパスに SPA ルートを作成すると競合が発生します)。

5.1.9.a authService をプロフィール関数で拡張する

src/services/authService.ts に追加します(関連する認証/プロフィールのロジックはすべて 1 か所にまとめ、PATCH の偽造防止トークンには既存の fetchAntiForgeryToken() ヘルパーを再利用してください)。

これらの正確な名前を使用してください — 実装者は patchOwnContact、getOwnContact、updateProfile のような、より短い名前や異なるスタイルの名前を勝手に考案してはなりません:

必須の名前種類説明
ProfileContactエクスポートされたインターフェースcontactid + 8 つの編集可能フィールド(firstname、lastname、mobilephone、address1_line1、address1_city、address1_stateorprovince、address1_postalcode、address1_country)を持つ型付きの連絡先形状。すべて string | null として型付けされます。emailaddress1 または middlename を含めないでください — email は useAuth().user.email から読み取り専用で表示され(そのための Web API ラウンドトリップは不要です)、middlename はシンプルなフォームから意図的に除外されています。
ProfileUpdateエクスポートされた型エイリアスPATCH ペイロード用の Partial<Omit<ProfileContact, 'contactid'>>
getMyProfileエクスポートされた async 関数シグネチャ: (contactId: string): Promise<ProfileContact>。credentials: 'same-origin' を指定して /_api/contacts({contactId})?$select=contactid,firstname,lastname,mobilephone,address1_line1,address1_city,address1_stateorprovince,address1_postalcode,address1_country を GET します($select の値は、フェーズ 8.1 の Webapi/contact/fields サイト設定にある 9 項目のリストと完全に一致しなければなりません)。非 OK の場合は例外をスローします。パースされた JSON を返します。開発モードではモックオブジェクトを返します。
updateMyProfileエクスポートされた async 関数シグネチャ: (contactId: string, payload: ProfileUpdate): Promise<void>。定義済みのフィールドのみを含むボディで /_api/contacts({contactId}) を PATCH します(undefined はスキップして、部分更新が影響を受けない列を空にしないようにします — ただし、ユーザーが明示的にクリアしたフィールドには null を送信してください)。ヘッダー: Content-Type: application/json、If-Match: *、fetchAntiForgeryToken() からの __RequestVerificationToken。非 OK の場合はパースされたエラーメッセージとともに例外をスローします。開発モードでは何もしない成功となります。
applyContactUpdateLocallyエクスポートされた関数シグネチャ: (payload: ProfileUpdate): void。payload から保存された firstname / lastname を window.Microsoft.Dynamic365.Portal.User に反映し戻します(firstName / lastName プロパティをその場で変更します — スナップショットでは camelCase、ペイロードでは小文字であることに注意してください)。window または Portal.User が利用できない場合は早期にリターンします。反映するのは firstname と lastname のみです — その他のフィールドはヘッダーでは使用されません。このヘルパーがないと、保存後にヘッダーは次回の完全なページリロードまで古い名前を表示し続けます — 5.1.9.b の「保存後のヘッダー更新」要件を参照してください。

完全なリファレンスコードは authentication-reference.md の「User Profile Page」セクションにあります。そのコードをそのままコピーしてください — 関数名を変更したり、シグネチャを変更したりしないでください。

また useAuth()(src/hooks/useAuth.ts)も更新してください — refresh() / loadUser() 内でユーザーオブジェクトをスプレッドさせます — setUser(getCurrentUser()) の代わりに setUser(current ? { ...current } : undefined) とします。理由: applyContactUpdateLocally が Portal.User をその場で変更した後、getCurrentUser() から返される参照は同じオブジェクトインスタンスです。React の setState は、新しい状態の参照が現在の参照と同一である場合に再レンダリングをスキップします。新しいオブジェクトにスプレッドすることで、React に新しい参照を認識させ、コンシューマーを再実行させるため、AuthButton が新しい名前で再描画されます。(Vue/Angular におけるフレームワーク相当の変更: ユーザーの ref/Subject が同じインスタンスではなく、新しい値を発行するようにしてください。)

5.1.9.b UserProfile.tsx を作成する

src/pages/UserProfile.tsx を作成します(ファイル名は必須です。Profile.tsx に名前を変更しないでください)。

2 つのセクション — 上部にアカウント詳細(読み取り専用)、その後に編集フォーム。それ以外は何もありません。

アカウント詳細セクション(読み取り専用、上部)

useAuth().user から取得した情報を、ちょうど2項目だけ表示するシンプルなブロックです。

ラベル値の参照元
氏名user.firstName + ' ' + user.lastName(結合した値。両方とも空の場合は「—」を表示)
メールアドレスuser.email

絶対に contactId、userRoles、サインインプロバイダー、ユーザー名、最終ログイン日時、その他のフィールドを表示しないでください。アカウント詳細セクションの目的は、どのアカウントでサインインしているかをユーザーに知らせることだけです。氏名とメールアドレスで十分です。

表示方法:2つのラベルと値の行を含む小さなカードまたは「アカウント詳細」という見出しを使用します。既存の CSS 変数を使用してください。編集可能であることを示す表示は不要です。「メールアドレスを編集」ボタンは追加しないでください。「アカウントを編集」リンクも追加しないでください。

ページのロジック
  • useAuth() から user、isAuthenticated、refresh を読み取ります。!isAuthenticated の場合は /login?returnUrl=/user-profile にリダイレクトします。
  • user.contactId が空、または存在しない場合は、「このアカウントではプロフィールを利用できません。連絡先に有効な ID が作成されるよう、認証プロバイダーの RegistrationClaimsMapping サイト設定を管理者に設定してもらってください。」と表示します(このスキルで前述した Entra のワークフォースにおけるクレームマッピングの不具合に対処します)。
  • マウント時に getMyProfile(user.contactId) を呼び出して、編集可能なフォームの現在値を取得します。読み込み状態を表示してください。
必須の編集可能フィールド(正確に8項目)

フォームには、以下の8項目を正確に含める必要があります。これより増やしたり減らしたりしないでください。メールアドレスはアカウント詳細で読み取り専用のため、追加しないでください。middlename は追加しないでください。「パスワードを変更」リンクは追加しないでください。「サインアウト」ボタンは追加しないでください。

フォームのラベルフォームフィールドの name 属性/連絡先列種類送信時に必須か
名firstnameテキストいいえ(すべて任意)
姓lastnameテキストいいえ
携帯電話mobilephone電話番号いいえ
住所1address1_line1テキストいいえ
市区町村address1_cityテキストいいえ
都道府県address1_stateorprovinceテキストいいえ
郵便番号address1_postalcodeテキストいいえ
国address1_countryテキストいいえ

すべてのフィールドは任意です。フォーム内の空文字列は、Web API には null として送信されます(ユーザーが値を消去できるようにするためです)。ページのレイアウトでは、「氏名」フィールド(firstname/lastname)、次に「連絡先」(mobile)、最後に「住所」(すべての address1_* フィールド)をグループ化します。レスポンシブな1〜2列のレイアウトには、grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)) を指定した CSS グリッドを使用します。

プロフィールページに含めてはならないもの

実行担当者は、以下の項目を追加してはなりません。既存の UI と重複するか、対象範囲外です。

  • ❌ 「パスワードを変更」リンクまたはボタン — パスワードのリセットは既存の /forgot-password フローで行います(リンクはログインページにあります)
  • ❌ プロフィールページの「サインアウト」ボタン — サインアウト操作はヘッダーの AuthButton ドロップダウンにのみあります
  • ❌ 編集可能なメールアドレスフィールド — メールアドレスは上部のアカウント詳細セクションに読み取り専用で表示されます
  • ❌ ミドルネームフィールド
  • ❌ ContactId、userRoles、サインイン方法、プロバイダー名、最終ログイン日時、その他のアカウントメタデータ
  • ❌ メールアドレスに関する注意書き(LoginClaimsMapping がメールアドレスの編集を上書きするという注意書きは、メールアドレスが編集不可になったため不要です)
  • ❌ アカウント削除または「アカウントを閉鎖」ボタン — 対象範囲外です
ロジックの詳細
  • Login.tsx / Registration.tsx の既存パターン(touched 状態、validateField、show() ヘルパー)を使って、フォーカスが外れたときに検証します

  • フィールド単位の検証:携帯電話番号は、空でない場合は最低 6 文字とします。その他の 7 フィールドには書式検証はありません。すべてのフィールドは任意です。空文字列は許可され、null として送信されます。

  • 送信時に updateMyProfile(contactId, payload) を呼び出します。成功した場合は、次の正確な順序で処理します。

    1. successMessage 状態に "Profile updated." を設定します。
    2. ローカルの profile スナップショットを更新します(次回保存時にフォームの差分計算が正しく行われるようにします)。
    3. applyContactUpdateLocally(payload) を呼び出します — 保存した名前フィールドを window.Microsoft.Dynamic365.Portal.User に反映します。これはヘッダーを再描画するために必須です — これを行わないと、ページ全体の読み込みを発生させるルートに移動するまで、ヘッダーに保存前の名前が表示され続けます。
    4. useAuth() の refresh() を呼び出します — 変更済みの Portal.User を再読み込みし、スプレッドを使って React の状態に反映します(上記の useAuth の更新)。これにより AuthButton が再レンダリングされます。スプレッドを使わない場合、同一の参照で setState が呼び出されるため React によってスキップされ、何も起きません。

    エラーの場合:サーバーエラーをインラインで表示します。

  • ブラウザーのタブタイトルを設定します — コンポーネントの先頭付近に useEffect を追加します(Login.tsx、Registration.tsx、ForgotPassword.tsx、ResetPassword.tsx、RedeemInvitation.tsx、ExternalLoginConfirmation.tsx、Terms.tsx などで使われている規約に合わせます)。

    useEffect(() => { document.title = 'My Profile — <SITE_NAME>' }, [])
    

    <SITE_NAME> は、Phase 1 で確認した実際のサイト表示名(他の認証ページで使われているものと同じ値。例:Contoso Portal)に置き換えます。書式は既存のページと完全に一致させる必要があります:<Page Title> — <Site Name>(全角ダッシュ — の前後にスペースを入れます)。|、:、ハイフンは使用しないでください。

  • 既存のスタイル規約を使用します(CSS 変数、カードレイアウト、最大幅約 580)。

  • 完全なリファレンス実装については、authentication-reference.md の「ユーザープロフィールページ」セクションを参照してください。

5.1.9.c AuthButton をドロップダウンに変更

src/components/AuthButton.tsx を更新します。認証済み状態では、INCLUDE_PROFILE_PAGE にかかわらず、ドロップダウン形式をデフォルトにします。これによりコンポーネントの一貫性を保ち、今後のメニュー項目の追加に備えます。

  • 未認証状態:変更なし — <Link to="/login" className="btn-primary">Sign In</Link>
  • 認証済み状態(新規):
    • トリガー:[Avatar] {displayName} ▾ — クリック可能
    • メニュー(useState<boolean> による状態管理):開いているときに表示
    • INCLUDE_PROFILE_PAGE = true の場合のメニュー項目:「マイプロフィール」(/user-profile への Link)と「サインアウト」(logout('/') を呼び出す)
    • INCLUDE_PROFILE_PAGE = false の場合のメニュー項目:「サインアウト」のみ
    • useEffect でドロップダウンのコンテナー外の mousedown を監視し、発生時に閉じます。Escape キーも監視し、押されたときに閉じます。アンマウント時にクリーンアップします。
    • 「マイプロフィール」リンクの onClick でもドロップダウンを閉じます。
    • ARIA:トリガーに aria-haspopup="menu" と aria-expanded={open} を設定します。メニューに role="menu" を設定します。各項目に role="menuitem" を設定します。
    • スタイル:メニューでは既存の CSS 変数(var(--color-surface)、var(--color-border)、var(--shadow-md)、var(--radius-sm))を使用します。
5.1.9.d ルートを追加する

src/App.tsx に追加します — 必ずこのパス(/user-profile、/profile ではない)を使用してください:

import UserProfile from './pages/UserProfile'
// ...inside <Routes>:
<Route path="/user-profile" element={<UserProfile />} />

⚠ <Route path="/profile" ... /> は使用しないでください。 サーバーは、レガシーのサーバーレンダリングページ用に /profile を予約しています。このスキルによって ProfileRedirectEnabled が false に設定されていても(これにより、サインイン後にサーバーがユーザーをそこへ自動リダイレクトすることが防がれます)、同じパスに SPA ルートを作成すると競合が発生します。常に /user-profile を使用してください。

認証ゲート: ページ自体が !isAuthenticated の場合の /login?returnUrl=/user-profile へのリダイレクトを処理します(5.1.9.b を参照)。好みの RequireAuth ラッパーコンポーネントがある場合は、<Route path="/user-profile" element={<RequireAuth><UserProfile /></RequireAuth>} /> を使用してもかまいません — ただし、そのラッパーがプロジェクトにすでに存在する場合に限ります。このページのためだけに新しい RequireAuth コンポーネントを導入しないでください。

保存後の動作

updateMyProfile が正常に解決された場合:

  1. インラインの successMessage state(「Profile updated.」)を設定します — フォーム上部の緑色のバナーで、閉じることができます
  2. useAuth().refresh() を呼び出します — window.Microsoft.Dynamic365.Portal.User を再読み込みします。Portal.User は初回ページ読み込み時のスナップショットであり、完全なページリロードが行われるまで新しい値を持たないことに注意してください。したがって、正しいパターンは、ローカルの UI state も更新することです(フォームフィールドはユーザーが入力したままの状態を維持し、ヘッダーのアバターは更新が反映されるまで一時的に古いイニシャルを表示する可能性があります)。この注意点をリファレンスに記載してください。
  3. ページにとどまります — 自動リダイレクトは行いません。
5.2 ナビゲーションへの統合

サイトのナビゲーションコンポーネントを見つけて、認証ボタンを統合します:

  1. サイトのソースコード内でナビゲーション/ヘッダーコンポーネントを検索します
  2. AuthButton コンポーネントをインポートします
  3. 既存のハードコードされたサインインリンク(例: <Link to="/login">Sign In</Link> や <a href="/signin">)を AuthButton コンポーネントに置き換えます。AuthButton は window.Microsoft.Dynamic365.Portal.User を読み取り、認証されていない場合は「Sign In」を、認証されている場合はユーザー名 + アバター + 「Sign Out」ボタンを動的に表示します。ハードコードされたリンクは認証状態に反応しません。
  4. 複数のプロバイダーが設定されている場合: AuthButton の「Sign In」アクションは /login ページへナビゲートする必要があります
  5. 単一プロバイダーの場合: AuthButton の「Sign In」アクションは login() を直接呼び出す必要があります
  6. 統合後に、Navbar にハードコードされたサインインリンクと AuthButton の両方が存在しないことを確認してください — ナビゲーション内の認証エントリポイントは必ず 1 つでなければなりません。
5.3 Git コミット

認証関連ファイルをステージしてコミットします:

git add -A
git commit -m "Add authentication service and auth UI component"
出力
  • 検出されたフレームワーク用の認証ボタンコンポーネントが作成されます
  • 認証ボタンがサイトのナビゲーションに統合されます
  • 登録ページが作成されます(オープン登録を伴うローカル認証が設定されている場合)
  • 変更が git にコミットされます

フェーズ 6: ロールベース UI の実装

目標: 保護されたコンテンツ領域を特定し、サイトのコンポーネントにロールベースの認可パターンを適用します。

アクション
6.1 保護されたコンテンツの特定

サイトのコンポーネントを分析し、ロールで制限すべきコンテンツを見つけます:

  • 管理者専用セクション(ダッシュボード、設定)
  • 認証済みユーザー専用コンテンツ(プロフィール、データビュー)
  • ロール固有の機能(編集ボタン、作成フォーム)

調査結果をユーザーに提示し、どの領域を保護するかを確認します。

6.2 認可パターンの適用

ユーザーの選択に基づいて、適切なコンポーネントをラップします:

React の例:

<RequireAuth fallback={<p>Please sign in to view this content.</p>}>
  <Dashboard />
</RequireAuth>

<RequireRole roles={['Administrators']} fallback={<p>Access denied.</p>}>
  <AdminPanel />
</RequireRole>

Vue の例:

<div v-role="'Administrators'">
  <AdminPanel />
</div>

Angular の例:

{ path: 'admin', component: AdminComponent, canActivate: [authGuard, roleGuard], data: { roles: ['Administrators'] } }
6.3 Git コミット

ステージしてコミットします:

git add -A
git commit -m "Add role-based access control to site components"
出力
  • 保護されたコンテンツ領域が特定され、ユーザーと確認されます
  • ロールベースの認可パターンがコンポーネントに適用されます
  • 変更が git にコミットされます

フェーズ 7: 認証設定の検証

目標: すべての認証関連ファイルが存在し、プロジェクトがビルドされ、認証 UI が正しくレンダリングされることを検証します。

アクション
7.1 ファイル一覧を確認する

次のファイルが作成されていることを確認してください。

  • src/types/powerPages.d.ts — Power Pages の型宣言
  • src/services/authService.ts — ログイン/ログアウト関数を含む認証サービス
  • フレームワーク固有の認証フック/コンポーザブル(例:React の場合は src/hooks/useAuth.ts)
  • src/utils/authorization.ts — ロール確認ユーティリティ
  • フレームワーク固有の認可コンポーネント(例:React の場合は RequireAuth.tsx、RequireRole.tsx)
  • 認証ボタンコンポーネント(例:React の場合は src/components/AuthButton.tsx)
  • 登録ページ(例:React の場合は src/pages/Registration.tsx)— ローカル認証が有効で、かつ REGISTRATION_MODE が Registration disabled 以外の場合(Open、Invitation-only、Both の各モードでは必ず作成されます)
  • パスワード忘れページ(例:React の場合は src/pages/ForgotPassword.tsx)— パスワードリセットを有効にしたローカル認証が設定されている場合のみ
  • セッション維持フック(例:React の場合は src/hooks/useSessionKeepAlive.ts)— Layout に統合
  • 利用規約ページ(例:React の場合は src/pages/Terms.tsx)— 利用規約が有効な場合のみ
  • パスワードリセットページ(例:React の場合は src/pages/ResetPassword.tsx)— パスワードリセットを有効にしたローカル認証が設定されている場合のみ
  • 招待引き換えページ(例:React の場合は src/pages/RedeemInvitation.tsx)— REGISTRATION_MODE が Invitation-only または Both の場合のみ
  • ユーザープロフィールページ(例:React の場合は src/pages/UserProfile.tsx)— INCLUDE_PROFILE_PAGE = true の場合のみ。また、authService.ts の getMyProfile と updateMyProfile 関数、ドロップダウン形式に変更された AuthButton、App.tsx の /user-profile ルートも必要です。
  • リダイレクトスクリプトを含む Code-Site-Shell-Header テンプレート(.powerpages-site/web-templates/code-site-shell-header/)— エントリーは有効な機能(resetpassword、redeeminvitation)によって異なります
  • headerwebtemplateid が Code-Site-Shell-Header を参照するように更新された website.yml
  • Web API のサイト設定(Webapi/contact/enabled、Webapi/contact/fields)— INCLUDE_PROFILE_PAGE = true の場合のみ
  • テーブル権限 .powerpages-site/table-permissions/My-Profile-Edit-Own-Contact.tablepermission.yml(scope: 756150004(Self)を含む)— INCLUDE_PROFILE_PAGE = true の場合のみ

各ファイルを読み、想定されるエクスポートと関数が含まれていることを確認してください。

  • 認証サービス:login、logout、getCurrentUser、isAuthenticated、fetchAntiForgeryToken、parseServerErrors、register、forgotPassword(ローカル認証の場合)、TermsRequiredError、acceptTerms(利用規約が有効な場合)、redeemInvitation と fetchInvitationDetails(招待モードの場合)、loginLocal が invitationCode パラメーターを受け取ること(招待モードの場合)
  • 認可ユーティリティ:hasRole、hasAnyRole、hasAllRoles、getUserRoles
  • ログインページと登録ページ:touched 状態、handleBlur、handleChange、showError ヘルパーを使用した、フォーカスが外れたときに検証するパターンを実装していること。利用規約が有効な場合は、どちらのページも TermsRequiredError を捕捉して /terms に遷移すること。招待モードの場合、ログインページは URL から invitationCode を読み取り、情報バナーを表示して値を引き継ぐこと。登録ページは fetchInvitationDetails() を呼び出してメールアドレスを事前入力すること。
  • 招待引き換えページ(招待モードの場合):URL からコードを事前入力すること、「登録せずに既存のアカウントでサインインする」チェックボックスがあること、redeemInvitation() の結果に応じて /registration または /login に分岐すること
  • セッション維持:Layout に統合され、/_layout/tokenhtml に ping を送信し、アクティビティを追跡し、セッションの有効期限切れを検出すること
7.2 ビルドを確認する

プロジェクトをビルドして、インポートエラー、型エラー、依存関係の不足を検出します。

npm run build

ビルドに失敗した場合は、先に問題を修正してください。

7.3 認証 UI の表示を確認する

開発サーバーを起動し、ナビゲーションに認証ボタンが表示されることを確認します。

npm run dev

Playwright を使用してサイトに移動し、スナップショットを取得して、認証ボタンが表示されていることを確認します。

  • http://localhost:<port> に移動します
  • ブラウザーのスナップショットを取得します
  • 認証ボタン(Sign In/モックユーザー)がナビゲーション領域に表示されていることを確認します

認証ボタンが表示されない場合、またはページのレンダリングエラーが発生した場合は、問題を修正します。

7.4 サイト整合性の最終ゲート

すべての認証ソースの変更、ビルド、ブラウザーでの確認が完了したら、次を実行します。

node "${PLUGIN_ROOT}/scripts/validate-site-integrity.js" --projectRoot "<PROJECT_ROOT>"

完了を報告したり、デプロイを依頼したりする前に、ブロッキングエラーをすべて修正します。内容を拡張したうえで、両方向のレビュー結果を確認し、報告します。その後にソースを変更した場合は、このゲートを再実行します。

出力
  • すべての認証ファイルを確認済み(存在し、想定されるエクスポートを含む)
  • プロジェクトのビルドが正常に完了
  • ブラウザーで認証 UI が正しく表示される

フェーズ 8:レビューとデプロイ

目標: 必要なサイト設定を作成し、すべての作業の概要を提示して、デプロイを促します。

アクション
8.1 サイト設定を作成する

サイトにはプロバイダー固有のサイト設定が必要です。.powerpages-site/site-settings/ が存在するか確認します。すべてのサイト設定には create-site-setting.js スクリプトを使用します。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "<Setting/Name>" \
  --value "<value>" \
  --description "<description>"

{ProviderName} の命名規則: {ProviderName} は、プロトコル名に連番を付けた値に置き換えます。

  • OpenID Connect:OpenIdConnect_1、OpenIdConnect_2 など
  • Entra External ID:OpenIdConnect_1(OIDC のパスを使用)
  • SAML2:SAML2_1、SAML2_2 など
  • WS-Federation:WsFederation_1、WsFederation_2 など

再実行時の処理: 設定がすでに存在するため create-site-setting.js が終了コード 1 で終了した場合は、その設定をスキップして続行します。既存の設定は前回の実行時にすでに構成されています。これを致命的なエラーとして扱わないでください。このスクリプトは、設定名とファイル名の両方で重複を確認します(大文字と小文字は区別されません)。既存の設定が上書きされることはありません。

重要 — 複数の OIDC プロバイダーを構成する場合の Redirect URI/CallbackPath の一意性:

OWIN OpenID Connect ミドルウェアでは、すべての OIDC プロバイダーの CallbackPath が既定で /signin-oidc になります。一意の CallbackPath 値を設定せずに 2 つの OIDC プロバイダー(例:Entra External ID と Okta)を構成すると、両方が /signin-oidc を使用するため、一方の認証がエラーを表示せずに失敗します。

ProviderName をそのまま CallbackPath のサフィックスとして使用します。 この方法は決定的で、ProviderName はすでに一意であるため、一意性が保証されます({ProviderName} の命名規則に基づく値:OpenIdConnect_1、OpenIdConnect_2、EntraExternalId など)。

すべての OIDC プロバイダー(Entra External ID を含む)で、次の正確なパターンを使用します。

  • CallbackPath = /signin-{ProviderName-lowercased}(例:/signin-entraexternalid、/signin-openidconnect_1)
  • RedirectUri = {site-url}/signin-{ProviderName-lowercased}(CallbackPath と完全に一致させる必要があります)

2 つの OIDC プロバイダーを使用する場合の例:

ProviderNameCallbackPathRedirectUri
EntraExternalId/signin-entraexternalidhttps://contoso.powerappsportals.com/signin-entraexternalid
OpenIdConnect_1(Okta)/signin-openidconnect_1https://contoso.powerappsportals.com/signin-openidconnect_1

サイト設定を作成する前に、既存の .powerpages-site/site-settings/ を読み込み、ほかの Authentication/OpenIdConnect/*/CallbackPath または RedirectUri の設定に同じ値がないことを確認します。同じ値が存在する場合(同じ ProviderName が再利用されている場合)は、ProviderName の数値サフィックスを増やし(例:OpenIdConnect_1 → OpenIdConnect_2)、CallbackPath を改めて決定します。

ID プロバイダーのアプリ登録(Microsoft Entra 管理センター → アプリの登録 → リダイレクト URI)に、正確な RedirectUri を登録するようユーザーに伝えます。 値の取得元:

  • シークレット以外の値(Authority URL、サイト URL、リダイレクト URI、AuthenticationType)→ フロー中に収集した情報から自動的に入力されます。ユーザーがファイルを編集する必要はありません。
  • ClientId / AppId → Phase 2.1 でユーザーから収集します(各プロバイダーの追加質問)。サイト設定を作成するときは、収集した値を使用してください。
  • シークレット(ClientSecret、AppSecret)→ create-environment-variable.js を使用して環境変数から設定してください。シークレットの値を直接尋ねたり、保存したりしないでください。以下の Phase 8.1.1 を参照してください。

必ず作成する項目 — 以下の設定はすべてのプロバイダータイプで必須です:

SPA サイトでは、ProfileRedirectEnabled は必ず false にしてください。 create-site-setting.js でこの設定がすでに存在すると報告された場合は、YAML ファイルを読み取り、値を確認してください。true の場合は、ファイルを編集して value: false に設定してください。これが true だと、サーバーは ReturnUrl に従わず、ログイン/登録後にユーザーを /profile にリダイレクトするため、SPA のフローが壊れます。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/ProfileRedirectEnabled" \
  --value "false" \
  --description "Disable profile redirect for SPA sites" \
  --type boolean

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/Enabled" \
  --value "true" \
  --description "Enable user registration (global toggle)" \
  --type boolean

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/ExternalLoginEnabled" \
  --value "true" \
  --description "Enable external identity provider login" \
  --type boolean

プロフィールのマッピング設定 — すべての外部プロバイダー(OIDC、SAML2、WS-Federation、ソーシャル)について、Phase 2.1 の PROFILE_MAPPING_CHOICE に基づいて RegistrationClaimsMapping を書き込み、PROFILE_SYNC_FREQUENCY = "Both" の場合は LoginClaimsMapping も書き込みます。

# Skip if PROFILE_MAPPING_CHOICE = "None"
# Generate the value based on PROFILE_MAPPING_CHOICE:
#   "Standard"           → firstname=given_name,lastname=family_name,emailaddress1=email
#   "Standard + phone"   → firstname=given_name,lastname=family_name,emailaddress1=email,mobilephone=phone_number
#   "Custom"             → user-provided comma-separated pairs
# For SAML2/WsFed, use the claim URI form instead of OIDC short names.

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/{Type}/{ProviderName}/RegistrationClaimsMapping" \
  --value "firstname=given_name,lastname=family_name,emailaddress1=email" \
  --description "Map IdP claims to contact fields on first sign-in"

# Only write if PROFILE_SYNC_FREQUENCY = "Both"
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/{Type}/{ProviderName}/LoginClaimsMapping" \
  --value "firstname=given_name,lastname=family_name,emailaddress1=email" \
  --description "Map IdP claims to contact fields on every login"

連絡先の紐付け設定 — CONTACT_LINKING_CHOICE に基づいて AllowContactMappingWithEmail を書き込みます。

# CONTACT_LINKING_CHOICE = "Link to existing"  → value "true"
# CONTACT_LINKING_CHOICE = "Create new"        → value "false" (or skip — false is the server default)
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/{Type}/{ProviderName}/AllowContactMappingWithEmail" \
  --value "<true-or-false-from-choice>" \
  --description "Auto-link external sign-in to existing contact by email match" \
  --type boolean

マルチテナントのガード:OIDC プロバイダーに対して AllowContactMappingWithEmail=true を書き込む前に、Authority URL を確認してください。/organizations/ または /common/ を含む場合、あるいは IssuerFilter がワイルドカードパターンに設定されている場合は、ユーザーの選択を false に上書きし、ユーザーに次のように伝えてください:「マルチテナントの Entra External ID 構成では、セキュリティ上の理由により連絡先のマッピングを使用できません(サーバーによって強制的に無効化されます)。連絡先のマッピングを有効にするには、特定のテナント GUID を指定したシングルテナントの Authority URL を使用してください。」

プロバイダーごとの RegistrationEnabled 設定 — すべての外部プロバイダー(OIDC、SAML2、WS-Federation、ソーシャル)について、次の設定も書き込みます。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/{Type}/{ProviderName}/RegistrationEnabled" \
  --value "true" \
  --description "Allow new users to register via this specific provider" \
  --type boolean

{Type} は OpenIdConnect、SAML2、WsFederation、または OpenAuth です。これは、グローバルな Authentication/Registration/ExternalLoginEnabled とは別のプロバイダーごとの切り替え設定です。登録を機能させるには、両方を true に設定してください。あるプロバイダーの RegistrationEnabled=false を設定する用途の例:既存ユーザーのサインインは許可したまま、特定の IdP からの新規ユーザー登録を一時的に禁止します。

プロバイダーごとの Caption — すべてのプロバイダーで必須です。 各設定済みプロバイダー({Type} = OpenIdConnect/SAML2/WsFederation/OpenAuth。ローカル認証にはログインボタンがないため、Caption は不要です)について、Phase 2.1 の表示名を使用して Authentication/{Type}/{ProviderName}/Caption(ログインボタンのラベル)を書き込みます。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/{Type}/{ProviderName}/Caption" \
  --value "<display-name from Phase 2.1>" \
  --description "Login button label for this provider"

ログアウト設定 — Phase 2.1.1 のログアウトモードの選択に応じて設定

外部プロバイダーの場合、Phase 2.1.1 でユーザーが**「フェデレーションログアウト」**を選択した場合にのみ、ログアウト設定を書き込みます。「ローカルログアウトのみ」(デフォルト)を選択した場合は、どちらの設定も書き込みません。サーバーのデフォルト値(RPInitiatedLogout=false、ExternalLogoutEnabled=false)により、適切に処理されます。

# Only when LOGOUT_MODE = "Federated logout"
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/RPInitiatedLogout" \
  --value "true" \
  --description "Federated logout — call IdP end_session_endpoint with id_token_hint" \
  --type boolean

# REQUIRED when RPInitiatedLogout=true — must be paired
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/PostLogoutRedirectUri" \
  --value "<POST_LOGOUT_REDIRECT_URI from Phase 2.1.1>" \
  --description "URL the IdP redirects to after federated logout completes"

SAML2/WS-Federation/ソーシャルプロバイダーでは、対応する設定名が異なります。Authentication/{Type}/{ProviderName}/RPInitiatedLogout と /PostLogoutRedirectUri が同じ命名パターンに従います。ソーシャルプロバイダー(Microsoft Account/Facebook/Google)では通常、プロバイダーがフェデレーションログアウトに対応していないため、この質問は該当しません。ソーシャルプロバイダーではログアウトモードの質問を省略し、どちらの設定も書き込まないでください。

サーバーの動作に関する注意:これらの設定がない場合(つまり「ローカルログアウトのみ」モード)、/Account/Login/LogOff は Power Pages セッションをクリアし、returnUrl クエリパラメーターにリダイレクトします(パラメーターがないか無効な場合はサイトのルートにリダイレクトします)。IdP セッションは維持されるため、次回のサインインはサイレント SSO になります。このモードでは、アプリ登録の変更は不要です。

これらの設定がある場合(「フェデレーションログアウト」)、/Account/Login/LogOff は id_token_hint と post_logout_redirect_uri を指定して、IdP の end_session_endpoint に 302 リダイレクトします。IdP はユーザーをサインアウトさせ、登録済みのログアウト後 URI にリダイレクトします。ユーザーはその URI も IdP のアプリ登録に登録する必要があります(フェーズ 2.1.1 を参照)。HAR 分析により、フロントチャネルログアウト URL がアプリ登録されていない場合、IdP はパラメーターをサイレントに破棄し、ユーザーが先に進めなくなることを確認しています。

プロバイダー固有の設定 — フェーズ 2.1 で選択した各プロバイダーのサイト設定を作成します。ユーザーが複数のプロバイダー(例:Entra External ID + Local Authentication)を選択した場合は、すべてのプロバイダーの設定を作成します。

Microsoft Entra ID — Authority、ClientId、Metadata などは、サイト作成時に Power Pages によって自動設定されます(プロバイダー名は AzureAD)。ただし、初回サインイン時に連絡先の firstname/lastname/email を設定するため、必ずクレームマッピングの設定を記述してください。自動設定された AzureAD プロバイダーには、デフォルトでクレームマッピングがありません。これらの設定がないと、新しい連絡先にはユーザーの oid が関連付けられるだけで、他のフィールドはすべて空になります(実証済みです。ワークフォース Entra ID の v1.0 トークンには email クレームが含まれず、明示的なマッピングが設定されていない限り、サーバーは given_name/family_name クレームから連絡先フィールドを自動生成しません)。

# RegistrationClaimsMapping — applied once at first sign-in, before contact is created.
# Uses `upn` for email because workforce Entra ID v1.0 tokens don't include the `email`
# claim by default. UPN (user principal name, e.g. [email protected]) is the standard
# substitute and matches what the user expects in their profile.
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/AzureAD/RegistrationClaimsMapping" \
  --value "firstname=given_name,lastname=family_name,emailaddress1=upn" \
  --description "Map AzureAD claims to contact fields on first sign-in (UPN as email — workforce v1.0 tokens lack email claim)"

# LoginClaimsMapping — applied every sign-in. Updates contact fields if user info
# changes in Entra (e.g., name change). Same mapping as Registration; safe to write
# both for consistency.
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/AzureAD/LoginClaimsMapping" \
  --value "firstname=given_name,lastname=family_name,emailaddress1=upn" \
  --description "Re-apply claim mapping on every sign-in so contact fields stay in sync with Entra"

なぜ email ではなく upn なのですか? ワークフォース Entra ID の OIDC エンドポイントは、デフォルトで v1.0 トークン(issuer sts.windows.net/{tid}/)を使用し、デフォルトでは email クレームを含みません。email クレームを取得するには、アプリ登録を v2.0 トークン用に設定し、email スコープを要求したうえで、ユーザーの Entra プロファイルに確認済みのメールアドレスが必要です(いずれもデフォルトではありません)。一方、upn クレームはワークフォースユーザーには常に含まれます。これはユーザープリンシパル名(例:[email protected])であり、ほとんどのワークフォースシナリオではメールアドレスとして機能します。これはワークフォース Entra ID(自動設定される AzureAD プロバイダー)にのみ適用されます。Entra External ID は適切な email クレームを含む v2.0 トークンを使用するため、マッピングは emailaddress1=upn ではなく emailaddress1=email にしてください(External ID についてはフェーズ 2.1 のプロファイルマッピングに関する質問を参照してください)。

この設定について質問はありません。 フェーズ 2.1 の一部としてプロファイルマッピングに関する質問(トラック B)がある他の外部プロバイダーとは異なり、Entra ID のマッピングは質問せずに記述されます。(a)ワークフォース Entra では、メールの代替として適切なクレーム(常に upn)が明確に決まっており、(b)このマッピングがないと連絡先が正しく作成されないため、設定を省略する意味がないためです。

OpenID Connect — プロバイダーの設定を作成します(ClientId はフェーズ 2.1.2 のセットアップから取得するか、手動で貼り付けます)。

# Authority (required — or use MetadataAddress as alternative)
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/Authority" \
  --value "<authority-url-from-user>" \
  --description "OIDC authority URL"

# MetadataAddress (optional — alternative to Authority for providers that need explicit metadata URL)
# Create this if the user provides a metadata URL distinct from the authority
# node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
#   --projectRoot "<PROJECT_ROOT>" \
#   --name "Authentication/OpenIdConnect/{ProviderName}/MetadataAddress" \
#   --value "<metadata-url>" \
#   --description "OIDC metadata endpoint URL"

# ClientId — use value collected in Phase 2.1
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/ClientId" \
  --value "<client-id-from-user>" \
  --description "Application client ID"

# AuthenticationType
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/AuthenticationType" \
  --value "<authority-url-from-user>" \
  --description "Provider identifier for ExternalLogin"

# RedirectUri — use /signin-{ProviderName-lowercased} for guaranteed uniqueness
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/RedirectUri" \
  --value "<site-url>/signin-{ProviderName-lowercased}" \
  --description "OAuth callback URL — unique per provider (matches CallbackPath)"

# CallbackPath — required to prevent collision when multiple OIDC providers exist
# OWIN defaults ALL OIDC providers to /signin-oidc
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/CallbackPath" \
  --value "/signin-{ProviderName-lowercased}" \
  --description "Unique callback path derived from ProviderName"

注: AuthenticationType の値は、ExternalLogin フォームの POST で使用される一意のプロバイダー識別子です。この値は、認証サービスで resolveProviderIdentifier() が返す値と一致する必要があります。 ログアウト設定 — ここでは RPInitiatedLogout や ExternalLogoutEnabled を自動的に書き込まないでください。サーバー側ではどちらもデフォルトで false です(StartupSettingsManager.cs:418,425 による)。つまり、ログアウト時にクリアされるのは Power Pages セッションのみで、IdP セッションは維持されます(ユーザーはサイレント SSO で再ログインできます)。これはよりシンプルなデフォルトであり、ほとんどのサイトが望む動作です。ユーザーが フェデレーションログアウト(IdP でもサインアウトし、post_logout_redirect_uri によってサイトへリダイレクトすること)を希望する場合は、Phase 2.1.1 の詳細設定フローで設定します。このフローでは RPInitiatedLogout=true と PostLogoutRedirectUri={SITE_URL}/ の両方をまとめて書き込みます(片方だけでは、ユーザーが IdP のサインアウト後のページから先に進めなくなります。手順全体については Phase 2.1.1 を参照してください)。

Entra External ID — Phase 2.1 の 4 ステップの手順で使用する値を使います。テナントのサブドメインとテナント ID から Authority と MetadataAddress を導出してください。ユーザーに貼り付けるよう求めないでください。

  • Authority = https://{EXTERNAL_ID_TENANT_SUBDOMAIN}.ciamlogin.com/{EXTERNAL_ID_TENANT_ID} — 末尾に /v2.0/ を付けないでください。Entra External ID はテナントのベアパスを使用します(従来の B2C や、通常 /v2.0/ が必要な Okta などの汎用 OIDC プロバイダーとは異なります)。
  • MetadataAddress = https://{EXTERNAL_ID_TENANT_SUBDOMAIN}.ciamlogin.com/{EXTERNAL_ID_TENANT_ID}/v2.0/.well-known/openid-configuration
  • AuthenticationType = Authority と同じ値(ExternalLogin POST のプロバイダー識別子と一致する必要があります)
  • ClientId = 手順のステップ 2 の EXTERNAL_ID_CLIENT_ID
  • RedirectUri = {SITE_URL}/signin-{ProviderName-lowercased} — ステップ 2 でユーザーに示される Redirect URI と同じ値
# Authority — derived: https://{subdomain}.ciamlogin.com/{tenantId}
# (do NOT append /v2.0/ — that breaks Entra External ID)
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/Authority" \
  --value "https://<EXTERNAL_ID_TENANT_SUBDOMAIN>.ciamlogin.com/<EXTERNAL_ID_TENANT_ID>" \
  --description "Entra External ID authority URL (derived)"

# MetadataAddress — derived: Authority + /v2.0/.well-known/openid-configuration
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/MetadataAddress" \
  --value "https://<EXTERNAL_ID_TENANT_SUBDOMAIN>.ciamlogin.com/<EXTERNAL_ID_TENANT_ID>/v2.0/.well-known/openid-configuration" \
  --description "OIDC metadata document URL (derived)"

# ClientId — from walkthrough Step 2
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/ClientId" \
  --value "<EXTERNAL_ID_CLIENT_ID>" \
  --description "Application client ID"

# AuthenticationType — must match Authority exactly (used as the 'provider' form value in ExternalLogin POST)
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/AuthenticationType" \
  --value "https://<EXTERNAL_ID_TENANT_SUBDOMAIN>.ciamlogin.com/<EXTERNAL_ID_TENANT_ID>" \
  --description "Provider identifier for ExternalLogin — must match Authority exactly"

# RedirectUri — the full URI the user registered in their Entra app.
# Confirmed/customized by user in Step 2 of walkthrough (stored as REDIRECT_URI).
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/RedirectUri" \
  --value "<REDIRECT_URI>" \
  --description "OAuth callback URL (must match the URI registered in the app registration)"

# CallbackPath — derived from RedirectUri (just the path portion, extracted via
# new URL(REDIRECT_URI).pathname). Required to prevent CallbackPath collision when
# multiple OIDC providers exist (OWIN defaults all OIDC to /signin-oidc otherwise).
# The user doesn't see this separately — the skill writes it automatically.
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/CallbackPath" \
  --value "<path-portion-of-REDIRECT_URI>" \
  --description "OWIN callback path (derived from RedirectUri)"

Entra External ID のログアウト設定 — ここでは RPInitiatedLogout や ExternalLogoutEnabled を自動的に書き込まないでください。サーバー側ではどちらもデフォルトで false です。デフォルトの動作では、ログアウト時にクリアされるのは Power Pages セッションのみで、ユーザーは Entra External ID テナントでサインインしたままです。次回のサインインでは、IdP の Cookie を使ってサイレント SSO が行われます。これはよりシンプルなデフォルトであり、顧客向けサイトの多くが望む動作です。ユーザーが フェデレーションログアウト(Entra External ID でもサインアウトすること)を希望する場合は、Phase 2.1.1 の詳細設定フローで有効にします。このフローでは RPInitiatedLogout=true と PostLogoutRedirectUri={SITE_URL}/ の両方をまとめて書き込み、さらにアプリ登録で Front-channel logout URL を設定する手順を追加します。

カスタムドメイン:ユーザーが Entra External ID テナントで *.ciamlogin.com ではなくカスタムドメイン(例:https://login.contoso.com/{tenantId}/v2.0/)を使用している場合は、上記で導出した値をカスタムドメインの値に置き換えてください。Phase 2.1 の手順では、現在カスタムドメインについて確認していません。既存のサイト設定にカスタムドメインの Authority がある状態で再実行する場合(Phase 1.5 の検出)、EXTERNAL_ID_TENANT_SUBDOMAIN から再構築せず、それらの値を維持してください。

デフォルトでは Entra External ID 用の ClientSecret ブロックはありません。 PKCE を使用するパブリッククライアントにクライアントシークレットは不要です。手順でも明示的にシークレットを求めません。このプロバイダーでは Phase 8.1.1(Key Vault)を完全にスキップしてください。デプロイ後にコンフィデンシャルクライアントのシナリオでシークレットが必要な場合は、Phase 8.5 の詳細な手動手順として記載してください(Power Pages 管理センター → Authentication settings から追加)。

ユーザーフロー名(EXTERNAL_ID_USER_FLOW)はサイト設定として書き込まれません。 Entra External ID ではユーザーフローがアプリ登録自体に関連付けられるため、Power Pages が URL やメタデータで名前を参照しなくても、サインイン時にユーザーフローが自動的に実行されます(従来の B2C とは異なります)。手順では、ユーザーがユーザーフローを作成済みであることを確認するためだけに名前を取得します。後からパスワードリセット用またはプロファイル編集用のユーザーフローを別途設定する場合は、詳細な上書き設定として PasswordResetPolicyId / ProfileEditPolicyId サイト設定を手動で追加できます。

SAML2 — プロバイダーの設定を作成します。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/SAML2/{ProviderName}/MetadataAddress" \
  --value "<metadata-url-from-user>" \
  --description "SAML IdP metadata URL"

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/SAML2/{ProviderName}/AuthenticationType" \
  --value "<site-url>" \
  --description "Provider identifier for ExternalLogin — MUST match providerIdentifier in authService exactly"

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/SAML2/{ProviderName}/ServiceProviderRealm" \
  --value "<site-url>" \
  --description "SP entity ID"

SAML2 では重要です: AuthenticationType サイト設定の値と、認証サービスコード内の providerIdentifier は、プロトコル(https:// と http:// の違い)、末尾のスラッシュ、大文字と小文字も含め、1 文字単位で完全に一致している必要があります。一致しないと、ログインはエラーを表示せずに失敗します。両方にまったく同じ <site-url> の値を使用してください。

WS-Federation — プロバイダーの設定を作成します。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/WsFederation/{ProviderName}/MetadataAddress" \
  --value "<metadata-url-from-user>" \
  --description "WS-Fed metadata URL"

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/WsFederation/{ProviderName}/AuthenticationType" \
  --value "<provider-realm-or-identifier>" \
  --description "Provider identifier for ExternalLogin"

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/WsFederation/{ProviderName}/Wtrealm" \
  --value "<site-url>" \
  --description "Relying party realm"

注: AuthenticationType の値は、認証サービスで resolveProviderIdentifier() が返す値と一致している必要があります。

ローカル認証 — Phase 2.1 でユーザーが選択した内容に基づいて、これらの設定を書き込みます。

登録モードにかかわらず、常に書き込む設定:

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/LocalLoginEnabled" \
  --value "true" \
  --description "Enable local username/password login" \
  --type boolean

# Set to "true" if the user chose email login, "false" if they chose username login (Phase 2.1)
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/LocalLoginByEmail" \
  --value "<true-or-false-from-user-choice>" \
  --description "Login by email (true) or username (false)" \
  --type boolean

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/ResetPasswordEnabled" \
  --value "true" \
  --description "Enable forgot password flow for local accounts" \
  --type boolean

登録モードの設定 — REGISTRATION_MODE から決定論的にマッピングします。

REGISTRATION_MODEAuthentication/Registration/EnabledAuthentication/Registration/OpenRegistrationEnabledAuthentication/Registration/InvitationEnabled
オープン登録のみtruetruefalse
招待制truefalsetrue
両方truetruetrue
登録無効false(スキップ — 該当なし)(スキップ — 該当なし)

外部プロバイダーのサイト(ローカル認証なし): ユーザーがローカル認証を選択しなかった場合、REGISTRATION_MODE は収集されません。代わりに、Phase 2.1.2(Phase 2.1.2 のオープン登録に関する質問)の OPEN_REGISTRATION_CHOICE を使用します。オープン — セルフサービスの場合は Authentication/Registration/OpenRegistrationEnabled = true、制限付き — 事前プロビジョニング済みユーザーのみの場合は false を書き込みます。この場合、Enabled/InvitationEnabled は書き込まないでください(これらはローカル登録ページを制御するものであり、外部認証のみのサイトではスキャフォールドされません)。REGISTRATION_MODE も OPEN_REGISTRATION_CHOICE も収集されていない場合は、これらの設定をスキップし、サーバーの既定値を適用します。

Authentication/Registration/RequireInvitationCode 設定は作成しないでください。 この設定はサーバーに存在せず(サーバーが読み取ることもありません)、招待制の動作は OpenRegistrationEnabled = false と InvitationEnabled = true の組み合わせだけで適用されます。このスキルの以前のバージョンではこの設定を書き込んでいました。プロジェクトのサイト設定(.powerpages-site/site-settings/Authentication-Registration-RequireInvitationCode.sitesetting.yml)にこの設定がある場合は、セットアップの一環としてファイルを削除してください。

例(該当する設定をそれぞれ書き込みます。モードが Registration disabled の場合は、Enabled=false を除き、Enabled/OpenReg/Invitation をスキップします):

# For all modes EXCEPT "Registration disabled":
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/Enabled" \
  --value "true" \
  --description "Master switch: registration is enabled" \
  --type boolean

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/OpenRegistrationEnabled" \
  --value "<true-or-false-from-mode>" \
  --description "Allow self-registration without an invitation" \
  --type boolean

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/InvitationEnabled" \
  --value "<true-or-false-from-mode>" \
  --description "Enable invitation-based registration" \
  --type boolean

# For "Registration disabled" mode ONLY:
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/Enabled" \
  --value "false" \
  --description "Disable all registration (only existing users can sign in)" \
  --type boolean

CAPTCHA の設定 — モードに応じて設定します。

REGISTRATION_MODECaptchaEnabled / IsCaptchaEnabledForRegistration理由
オープン登録のみ/両方false(注記付き)SPA の登録フォームではサーバー側 CAPTCHA ウィジェットをレンダリングできません。有効のままにすると、登録はエラーを表示せずに失敗します。本番環境では、サイト所有者が独自のクライアント側 CAPTCHA ソリューションを追加し、サーバー設定を再び有効にしてください。
招待制false招待によってすでにユーザーが絞り込まれるため、CAPTCHA を追加してもセキュリティ上の効果はなく、手間が増えるだけです。
登録無効(スキップ — 登録は無効)—
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/CaptchaEnabled" \
  --value "false" \
  --description "Disable server-rendered CAPTCHA — SPA cannot render the widget" \
  --type boolean

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/IsCaptchaEnabledForRegistration" \
  --value "false" \
  --description "Disable server-rendered CAPTCHA on registration form" \
  --type boolean

contact エンティティの Web API — INCLUDE_PROFILE_PAGE = true の場合に設定

ユーザーがプロフィールページを選択した場合(Phase 2.1)、contact エンティティの Web API を有効にします。これらの設定により、/_api/{entity} 経由でアクセスできるエンティティとフィールドが Power Pages サーバーに通知されます。これによって SPA の getMyProfile と updateMyProfile は、ユーザーの contact レコードを読み書きできます。

⚠ Webapi/contact/fields の必須値 — この完全な文字列をそのまま使用してください。実行者はこれをトリミングしてはいけません。プロフィールフォームですべての編集可能なフィールドを読み書きできるよう、9 項目すべてが必要です。

contactid,firstname,lastname,mobilephone,address1_line1,address1_city,address1_stateorprovince,address1_postalcode,address1_country

これは contactid(レコードの識別用)と、Phase 2.1 / Phase 5.1.9.b に記載されている 8 つの編集可能なフィールドです。emailaddress1 はこのリストに含まれていません。メールアドレスは useAuth().user.email から取得され、アカウント詳細セクションに読み取り専用で表示されるため、Web API とのやり取りは不要です。middlename もこのリストに含まれていません。シンプルなフォームでは意図的に除外されています。上記のリストから項目を省略すると、対応するフォーム入力が Web API からの 403 によって何も表示されず失敗します。

# Enable Web API on the contact table
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Webapi/contact/enabled" \
  --value "true" \
  --description "Enable Web API access for contact table (profile page)" \
  --type boolean

# Allowed fields — USE THE EXACT VALUE BELOW (9 entries, all lowercase)
# Case-sensitive: PascalCase or Title Case produces 403 even if the column exists
node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Webapi/contact/fields" \
  --value "contactid,firstname,lastname,mobilephone,address1_line1,address1_city,address1_stateorprovince,address1_postalcode,address1_country" \
  --description "Fields allowed via Web API for profile page (read + write)"

フィールド名の形式:fields の値に含めるすべての項目は、小文字の Dataverse LogicalNames でなければなりません。Web API は大文字と小文字を区別して文字列を照合するため、列が存在していても、FirstName や Firstname を指定するとサーバーから 403 Forbidden が返されます。上記のリストが正しい値です。

後からフィールドリストをカスタマイズする場合:ユーザーがプロフィールページに表示したいカスタム連絡先列(例:cr123_jobtitle)を持っている場合、スキルの実行後にこの fields の値を拡張し、UserProfile.tsx の ProfileContact インターフェースとフォームに対応するフィールドを追加できます。スキル自体には、上記の 9 項目を正確に含めてください。スキャフォールディング中に項目を追加または削除してはいけません。

8.1.x 連絡先のテーブル権限を作成する(INCLUDE_PROFILE_PAGE = true の場合)

INCLUDE_PROFILE_PAGE = true の場合のみ実行します。 それ以外の場合はスキップしてください。

連絡先テーブルで Web API を有効にするだけでは不十分です。Power Pages サーバーでは、サインイン中のユーザーに読み取り/書き込みアクセスを許可する、一致する adx_entitypermission レコードも必要です。このレコードがないと、サイト設定でエンティティが許可されていても、すべての /_api/contacts(...) 呼び出しが 403 を返します。

プロフィールページのユースケースでは、Self スコープ(756150004) が適切です。これにより、ユーザー自身の連絡先レコードにのみアクセスが許可されます。create-table-permission.js スクリプトはこれを検証し、連絡先テーブル自体に対する Contact スコープを拒否します。

手順 1 — Authenticated Users Web ロールの UUID を確認します。

Phase 1.4 では、すでに Web ロールの一覧を取得しています。.powerpages-site/web-roles/*.yml から authenticatedusersrole: true のロールを見つけてください。一般的なファイル名は Authenticated-Users.webrole.yml です。その id フィールドを取得してください。

該当するロールがない場合は、ユーザーに警告します。

"このサイトには 'Authenticated Users' Web ロールがありません。authenticatedusersrole: true を持つロールを作成してユーザーに割り当てるまで、どのユーザーもプロフィールを編集できません。/create-webroles を実行してロールを追加してから、このスキルを再実行してください。"

ロールが見つからない場合は、このフェーズの残りをスキップしてください。

手順 2 — テーブル権限を作成します。

node "${PLUGIN_ROOT}/scripts/create-table-permission.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --permissionName "My Profile - Edit Own Contact" \
  --tableName "contact" \
  --webRoleIds "<authenticated-users-role-uuid>" \
  --scope "Self" \
  --read \
  --write

このスクリプトは .powerpages-site/table-permissions/My-Profile-Edit-Own-Contact.tablepermission.yml を作成します。

adx_entitypermission_webrole:
- <authenticated-users-role-uuid>
append: false
appendto: false
create: false
delete: false
entitylogicalname: contact
entityname: My Profile - Edit Own Contact
id: <generated-uuid>
read: true
scope: 756150004
write: true

注:create: false + delete: false — ユーザーは自身の連絡先を読み取り、更新できますが、新しい連絡先を作成したり、既存の連絡先を削除したりすることはできません。これはプロフィール編集における最小権限の原則に基づくデフォルト設定です。

権限境界の検証:Self スコープを使用している場合、ユーザーが DevTools から PATCH /_api/contacts({someone-elses-contact-id}) のような悪意のあるリクエストを作成しても、サーバーは 403 を返します。Self スコープは、セッション Cookie に含まれるサインイン中のユーザーの contactid に基づいて、行レベルのセキュリティを適用します。

Facebook — ClientId ではなく AppId を使用します。App ID は Phase 2.1 で収集済みです。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenAuth/Facebook/AppId" \
  --value "<app-id-from-user>" \
  --description "Facebook App ID"

Google — Client ID は Phase 2.1 で収集済みです。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenAuth/Google/ClientId" \
  --value "<client-id-from-user>" \
  --description "Google Client ID"

Microsoft Account — Client ID は Phase 2.1 で収集済みです。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenAuth/MicrosoftAccount/ClientId" \
  --value "<client-id-from-user>" \
  --description "Microsoft Account Client ID"
8.1.1 Azure Key Vault を使用してシークレットを処理する

プロバイダーがシークレットを必要とする場合にのみ、このフェーズを実行してください。 設定済みのプロバイダーにシークレットが不要な場合は、フェーズ全体をスキップしてください。 シークレットが必要になる場合があるプロバイダー:

  • OpenID Connect — デフォルトでは不要です。Power Pages のデフォルトの code id_token フローは、シークレット不要のパブリッククライアントです(Phase 2.1.2 と idp-provisioning-reference.md を参照)。シークレットが必要なのは、機密クライアントへの上書き設定を行う場合のみです。ユーザーが明示的に希望しない限り、このセクションはスキップしてください。
  • Entra External ID — デフォルトでは不要です。Phase 2.1 の手順では、Entra External ID を PKCE を使用するパブリッククライアント(クライアントシークレットなし)として構成します。Entra External ID では、このセクションを必ずスキップしてください。 ユーザーが後でシークレットを使う機密クライアントの設定を必要とする場合は、Power Pages 管理センターから ClientSecret を手動で追加します。この設定は、Phase 8.5 のデプロイ後の注意事項で説明しています。
  • Microsoft Account / Facebook / Google — 必要です(ソーシャル OAuth ではアプリシークレットが必要です)
  • SAML2 / WS-Federation — 不要です(シークレットではなく、証明書を使用します)
  • ローカル認証 — 不要です
  • Microsoft Entra ID — 不要です(Power Pages 管理センターから構成します)

どのプロバイダーでもシークレットが不要な場合は、この Phase 8.1.1 全体をスキップして、招待のブロックに進んでください。

シークレット(ClientSecret、AppSecret)は、サイト設定の YAML ファイルやプレーンテキストの環境変数には絶対に保存しないでください。Azure Key Vault にシークレットを保存し、--type secret を指定した Dataverse 環境変数から参照してください。

ステップ 1 — 利用可能な Key Vault を一覧表示します:

node "${PLUGIN_ROOT}/scripts/list-azure-keyvaults.js"

ステップ 2 — Key Vault を選択または作成します:

Key Vault が見つかった場合は、どれを使用するか尋ねます:

質問コンテキスト
認証シークレットの保存に使用する Azure Key Vault を選択してください。スクリプトの出力から名前を提示します

Key Vault が見つからない場合:

質問オプション
Azure Key Vault が見つかりませんでした。作成しますか?新しい Key Vault を作成する(推奨)、Key Vault をスキップする — シークレットは後で設定します

「新しい Key Vault を作成する」を選択した場合:Vault 名、リソースグループ、場所を尋ねます:

node "${PLUGIN_ROOT}/scripts/create-azure-keyvault.js" \
  --name "<vault-name>" \
  --resourceGroup "<resource-group>" \
  --location "<location>"

「Key Vault をスキップする」を選択した場合:「フォールバック」までスキップします。

ステップ 3 — 各シークレットを Key Vault に保存するようユーザーに指示します:

シークレットの値を尋ねてはいけません。シークレットの値が会話を通じて渡されることは、絶対にあってはなりません。両方の方法を提示します:

オプション A — Azure CLI(推奨):

For each secret, run the following command (replacing <YOUR_SECRET_VALUE> with the actual value):

1. <Provider> Client Secret:
   printf '%s' '<YOUR_SECRET_VALUE>' | node "${PLUGIN_ROOT}/scripts/store-keyvault-secret.js" \
     --vaultName "<selected-vault>" \
     --secretName "<provider>-client-secret"

各コマンドは secretUri を含む JSON オブジェクトを出力することを伝え、ワークフローを続行できるよう、その出力を共有してもらいます。

オプション B — Azure Portal:

1. Go to https://portal.azure.com → Key vaults → <selected-vault> → Secrets
2. Click "+ Generate/Import"
3. Name: <provider>-client-secret, Value: paste your secret
4. Click "Create", then click the secret → current version → copy "Secret Identifier" URI
5. Share the URI here so the workflow can continue

ステップ 4 — Dataverse に環境変数を作成します(種類:シークレット):

ユーザーから secretUri を受け取った後、Key Vault のシークレットを参照する環境変数を作成します:

node "${PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
  --schemaName "<prefix_ProviderClientSecret>" \
  --displayName "<Provider> Client Secret" \
  --type "secret" \
  --value "<secretUri-from-step-3>"

ステップ 5 — 環境変数用のサイト設定を作成します:

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/OpenIdConnect/{ProviderName}/ClientSecret" \
  --envVarSchema "<prefix_ProviderClientSecret>"

これにより、envvar_schema と source: 1 を含むサイト設定が作成されます。これは、Power Pages に Dataverse 環境変数(Key Vault によって保護)から値を解決するよう指示します。

選択したプロバイダーごとに、ステップ 3〜5 を繰り返します:

プロバイダーシークレット名サイト設定環境変数スキーマ
OIDC / Entra External ID{provider}-client-secretAuthentication/OpenIdConnect/{ProviderName}/ClientSecret{prefix}_ProviderClientSecret
Facebookfacebook-app-secretAuthentication/OpenAuth/Facebook/AppSecret{prefix}_FacebookAppSecret
Googlegoogle-client-secretAuthentication/OpenAuth/Google/ClientSecret{prefix}_GoogleClientSecret
Microsoft Accountmicrosoft-client-secretAuthentication/OpenAuth/MicrosoftAccount/ClientSecret{prefix}_MicrosoftClientSecret

フォールバック — ユーザーが Key Vault をスキップした場合:

ユーザーが Key Vault を使用しないことを選択した場合は、プレースホルダー値を持つ環境変数を作成します(シークレット型ではなく、通常の文字列型)。ユーザーは後で Power Apps メーカー ポータルから更新します:

node "${PLUGIN_ROOT}/scripts/create-environment-variable.js" "<ENV_URL>" \
  --schemaName "<prefix_ProviderClientSecret>" \
  --displayName "<Provider> Client Secret" \
  --value "PLACEHOLDER_SET_ACTUAL_VALUE"

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "<site-setting-name-from-table-above>" \
  --envVarSchema "<prefix_ProviderClientSecret>"

各プレースホルダーを次の手順で更新するようユーザーに伝えてください:

  • Power Apps maker portal(make.powerapps.com)→ Solutions → Default Solution → Environment variables → 表示名で検索 → 値を更新

更新が必要な環境変数の一覧(それぞれの表示名とスキーマ名)を提示してください。

Two-Factor Authentication — サポートされていません。 スキルで Authentication/Registration/TwoFactorEnabled、Authentication/Registration/RememberMeEnabled、Authentication/Registration/RememberBrowserEnabled のサイト設定を作成してはいけません。Power Pages の組み込み 2FA フローでは、サーバー側でレンダリングされる /Account/Login/SendCode ページと /Account/Login/VerifyCode ページが使用されます。2FA トークンの状態は、資格情報の POST とコード検証の POST の間、サーバー側の Cookie に保存されます。また、提供できる SPA 相当の UI はありません。2FA が必要な場合は、ID プロバイダー層で有効にすることをユーザーに推奨してください(Entra External ID の条件付きアクセス、B2C のユーザーフロー MFA、Auth0 Guardian、Okta Verify など)。IdP レベルの 2FA は Power Pages に対して透過的に機能し、ユーザー体験全体が IdP のブランド化されたエクスペリエンス内で完結します。

Terms and Conditions — 利用規約を有効にする場合:

前提条件:Dataverse 環境に GDPR/Privacy Extensions ソリューション(msdynce_PortalPrivacyExtensions)がインストールされている必要があります。このソリューションがない場合、サーバーは TermsAgreementEnabled を完全に無視します。この要件をユーザーに伝えてください。

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/TermsAgreementEnabled" \
  --value "true" \
  --description "Require terms acceptance before accessing the site" \
  --type boolean

ユーザーが TermsPublicationDate を指定した場合:

node "${PLUGIN_ROOT}/scripts/create-site-setting.js" \
  --projectRoot "<PROJECT_ROOT>" \
  --name "Authentication/Registration/TermsPublicationDate" \
  --value "<ISO-date-from-user>" \
  --description "Users who accepted before this date will be re-prompted"

必須のコンテンツスニペット Account/Signin/TermsAndConditionsCopy を .powerpages-site/content-snippets/ に作成してください。このスニペットには空でないコンテンツが必要です。これがない場合、設定が true であっても、サーバーは利用規約を無効にします。スニペットのコンテンツは、SPA の Terms ページにハードコードされた TERMS_CONTENT 定数と一致する必要があります。

コンテンツスニペットのディレクトリが存在するか確認し、スニペットの YAML ファイルを作成してください。形式は .powerpages-site/content-snippets/ にある既存のスニペットのパターンに従ってください。コンテンツスニペットを作成するスクリプトがあれば、それを使用してください。なければ、既存のスニペットのパターンに従って YAML ファイルを手動で作成してください。

任意で、サーバー側でレンダリングされる利用規約ページ用の他の 3 つのスニペットも作成してください(SPA が読み込まれていない場合、たとえばディープリンクからのアクセス時に使用されます)。

  • Account/Signin/TermsAndConditionsHeading
  • Account/Signin/TermsAndConditionsAgreementText
  • Account/Signin/TermsAndConditionsButtonText
8.2 スキルの使用状況を記録する

参照先:${PLUGIN_ROOT}/references/skill-tracking-reference.md

参照先のスキル追跡手順に従って、このスキルの使用状況を記録してください。--skillName "SetupAuth" を使用してください。

8.3 作成内容の概要

作成したすべての項目の概要を示します。

コンポーネントファイルステータス
型宣言src/types/powerPages.d.ts作成済み
認証サービスsrc/services/authService.ts作成済み
認証フック/コンポーザブルsrc/hooks/useAuth.ts(またはフレームワーク相当のもの)作成済み
認可ユーティリティsrc/utils/authorization.ts作成済み
認証コンポーネントRequireAuth、RequireRole(またはフレームワーク相当のもの)作成済み
認証ボタンsrc/components/AuthButton.tsx(またはフレームワーク相当のもの)作成済み
登録ページsrc/pages/Registration.tsx(またはフレームワーク相当のもの)— ローカル認証用。無効化されていないこと作成済み(該当する場合)
招待引き換えページsrc/pages/RedeemInvitation.tsx(またはフレームワーク相当のもの)— Invitation-only または Both モード作成済み(該当する場合)
パスワードを忘れた場合のページsrc/pages/ForgotPassword.tsx(またはフレームワーク相当のもの)— ローカル認証のみ作成済み(該当する場合)
セッション維持機能src/hooks/useSessionKeepAlive.ts(またはフレームワーク相当のもの)— Layout に統合作成済み
利用規約ページsrc/pages/Terms.tsx(またはフレームワーク相当のもの)— 利用規約が有効な場合作成済み(該当する場合)
利用規約のスニペットAccount/Signin/TermsAndConditionsCopy のコンテンツスニペット作成済み(該当する場合)
パスワードリセットページsrc/pages/ResetPassword.tsx(またはフレームワーク相当のもの)— ローカル認証のみ作成済み(該当する場合)
ユーザープロフィールページsrc/pages/UserProfile.tsx(またはフレームワーク相当のもの)— INCLUDE_PROFILE_PAGE = true の場合。さらに、AuthButton のドロップダウン、authService の getMyProfile/updateMyProfile、/user-profile ルート作成済み(該当する場合)
シェルヘッダーCode-Site-Shell-Header ウェブテンプレート — サーバー認証ページから SPA にリダイレクト作成済み(アップロード後も維持)
サイト設定登録モードに応じた ProfileRedirectEnabled = false、Enabled、OpenRegistrationEnabled、InvitationEnabled作成済み
Web API サイト設定Webapi/contact/enabled = true、Webapi/contact/fields = ... — INCLUDE_PROFILE_PAGE = true の場合作成済み(該当する場合)
テーブル権限My Profile - Edit Own Contact(contact、Self スコープ、読み取り+書き込み、Authenticated Users ロール)— INCLUDE_PROFILE_PAGE = true の場合作成済み(該当する場合)
8.3.5 HTML セットアップレポートを生成する

概要の後に、今回の実行におけるすべての決定事項と成果物をまとめた HTML セットアップレポートを <PROJECT_ROOT>/docs/auth-setup-report.html に生成します。レポートはユーザーのブラウザーで開かれ、後から確認できる、共有可能な記録として残ります。

理由:このスキルでは、プロバイダーの選択、登録モード、プロファイルのマッピング、連絡先のリンク、プロファイルページ、利用規約、フェデレーションログアウトなど、多くの設定を決定します。HTML レポートなら設定全体を並べて確認しやすく、上記のチャット概要よりもはるかに見渡しやすくなります。また、スキルを再実行しなくても、レビュアーやチームメイトが確認できる成果物を 1 つにまとめられます。

1. データペイロードを作成します。 次のキーを持つ JSON オブジェクトを構築し、一時ファイルに書き込みます。

{
  "META_DATA": {
    "siteName": "<SITE_NAME>",
    "reportDate": "<YYYY-MM-DD>",
    "framework": "<React|Vue|Angular|Astro>",
    "nextStepsHtml": "<HTML string — the same provider-specific guidance from 8.5, formatted as an <ol>>"
  },
  "PROVIDERS_DATA": [
    {
      "type": "Entra ID | Entra External ID | OIDC | SAML2 | WS-Federation | Local | Social (Microsoft|Facebook|Google)",
      "displayName": "<friendly name shown in the login UI>",
      "name": "<ProviderName used in site setting keys>",
      "identifier": "<providerIdentifier / authority URL / issuer URI>",
      "authority": "<Authority site-setting value, if applicable>",
      "clientId": "<ClientId site-setting value, if applicable>",
      "redirectUri": "<Computed redirect URI, if applicable>",
      "scopes": "<Scopes site-setting value, if applicable>",
      "registrationClaimsMapping": "<RegistrationClaimsMapping value, if applicable>",
      "loginClaimsMapping": "<LoginClaimsMapping value, or null when sync = First sign-in only>",
      "contactLinking": "Link to existing contact by email | Create a new contact",
      "profileSync": "First sign-in only | Both",
      "federatedLogout": "Enabled | Disabled",
      "isPrimary": true | false
    }
  ],
  "LOCAL_AUTH_DATA": {
    "loginBy": "Email | Username",
    "registrationMode": "Open | Invitation-only | Both | Disabled",
    "resetPasswordEnabled": true | false,
    "emailConfirmationEnabled": true | false
  },
  "OPTIONAL_FEATURES_DATA": {
    "profilePage": true | false,
    "termsAndConditions": true | false,
    "termsEnforced": true | false,
    "federatedLogout": true | false,
    "sessionKeepAlive": true | false
  },
  "SITE_SETTINGS_DATA": [
    { "name": "Authentication/Registration/ProfileRedirectEnabled", "value": "false" }
  ],
  "TABLE_PERMISSIONS_DATA": [
    { "name": "My Profile - Edit Own Contact", "table": "contact", "scope": "Self (756150004)", "read": true, "write": true, "create": false, "delete": false }
  ],
  "FILES_DATA": [
    { "path": "src/services/authService.ts", "action": "Created | Updated", "notes": "<short description>" }
  ]
}
  • ローカル認証が設定されていない場合は、LOCAL_AUTH_DATA を null に設定します。
  • スキルが作成したすべてのサイト設定を SITE_SETTINGS_DATA(Phase 8.1)に含めます。ProfileRedirectEnabled、すべての Authentication/{Type}/{Name}/... ブロック、該当する場合は Webapi/contact/* などが含まれます。名前に Secret を含む設定は値を *** に置き換えてマスキングします(レポートにシークレットを表示してはいけません)。
  • スキルが作成または更新したすべての YAML/コードファイルを FILES_DATA に含めます。新規ファイルにはアクション "Created"、編集したファイルには "Updated" を使用します。
  • nextStepsHtml は、下記のセクション 8.5 の案内に沿って、コマンドを <code> タグで囲んだ <ol>...</ol> として書式設定します。実際に設定したプロバイダーに該当する手順のみを含めます。

2. レポートをレンダリングします。 次を実行します。

node "${PLUGIN_ROOT}/scripts/render-auth-report.js" \
  --output "<PROJECT_ROOT>/docs/auth-setup-report.html" \
  --data "<path-to-temp-data.json>"

レンダラーは既存ファイルへの上書きを拒否します。このパスに以前のレポートが存在する場合は、日付のサフィックスを追加します。例:auth-setup-report-2026-05-27.html。

3. ブラウザーでレポートを開きます(可能な範囲で実行します。スキルの処理を妨げてはいけません)。

  • Windows:start "" "<PROJECT_ROOT>/docs/auth-setup-report.html"
  • macOS:open "<PROJECT_ROOT>/docs/auth-setup-report.html"
  • Linux:xdg-open "<PROJECT_ROOT>/docs/auth-setup-report.html"

4. レポートの絶対パスをユーザーに伝えます。 ブラウザーを開けなかった場合でも、ユーザーが手動で開けるようにします。文例:「セットアップレポート全体を <path> に作成し、ブラウザーで開きました。このファイルをいつでも再確認すれば、今回の実行におけるすべての決定事項と成果物を確認できます。」

8.4 デプロイするか尋ねる

🚦 ゲート(plan · setup-auth:8.4.deploy): 最終デプロイの確認 — サイト設定はデプロイとともに反映されるため、デプロイするまで認証は機能しません。

トリガー: すべての認証ファイルが作成され、検証済みであること。 確認する理由: 自動デプロイでは、誤った環境が選択されます。 キャンセルした場合: 何も起きません。認証関連の成果物はディスクに残り、デプロイは実行されません。

AskUserQuestion を使用します。

質問選択肢
認証と認可が設定されました。ログインを機能させるには、サイトをデプロイする必要があります。今すぐデプロイしますか?はい、今すぐデプロイする(推奨)、いいえ、後でデプロイする

「はい、今すぐデプロイする」の場合:/deploy-site を呼び出します。

「いいえ」の場合:ユーザーに次のように伝えます。

「準備ができたら、/deploy-site を使用してサイトをデプロイしてください。新しいサイト設定を反映してサイトをデプロイするまで、認証は機能しません。」

8.5 デプロイ後の注意事項

デプロイ後(またはデプロイを見送った場合)、プロバイダー固有の案内をユーザーに伝えます。

  • デプロイ済みサイトでテスト:認証はデプロイ済みの Power Pages サイトでのみ機能し、localhost では機能しません
  • ID プロバイダーの構成:プロバイダーごとに設定が必要です。
    • Entra ID:Power Pages 管理センターで ID プロバイダーを構成します
    • OpenID Connect:OIDC プロバイダーにクライアントアプリケーションを登録し、ClientId サイト設定を更新します。プロバイダーでリダイレクト URI を {site-url}/signin-{ProviderName-lowercased} に設定します
    • SAML2:SAML IdP にサイトをサービスプロバイダー(SP)として登録します。ServiceProviderRealm と AssertionConsumerServiceUrl はサイト URL と一致させる必要があります
    • WS-Federation:WS-Fed プロバイダーにサイトを証明書利用者として登録します
    • ローカル認証:外部プロバイダーは不要です。ユーザーはサイト上でユーザー名とパスワードを使って直接登録し、ログインします
    • Microsoft Account:Azure ポータルでアプリケーションを登録し、Power Apps メーカー ポータルから ClientSecret 環境変数を更新します。シークレットをソース管理にコミットしないでください
    • Facebook:Facebook Developer Console でアプリケーションを登録し、Power Apps メーカー ポータルから AppSecret 環境変数を更新します。シークレットをソース管理にコミットしないでください
    • Google:Google Cloud Console でアプリケーションを登録し、Power Apps メーカー ポータルから ClientSecret 環境変数を更新します。シークレットをソース管理にコミットしないでください
    • Entra External ID:Entra External ID テナントにアプリケーションを登録します。ClientId サイト設定を更新します。リダイレクト URI を {site-url}/signin-{ProviderName-lowercased} に設定します。authority URL には {tenant}.ciamlogin.com またはカスタムドメインを使用できます。
  • 認証失敗の処理(ユーザーを SPA 内に留める):OIDC/SAML2/WS-Fed 認証が失敗すると、サーバーは /Account/Login/ExternalAuthenticationFailed にリダイレクトします。これはサーバーレンダリングされたページで、SPA を中断します。失敗時もユーザーを SPA 内に留めるには、Power Pages 管理センターで Dataverse のコンテンツ スニペット Account/Register/ExternalAuthenticationFailed と Account/Register/ExternalAuthenticationFailed/AccessDenied を編集し、<script> を挿入して /login?message={error-code} にリダイレクトします。これにより、SPA の getAuthError() がエラーをインライン表示します。正確なスクリプトについては authentication-reference.md を参照してください。
  • ユーザープロフィールの表示:ログイン後、認証サービスの getUserDisplayName() は firstName + lastName → firstName → lastName → email → userName → 'User' の順にフォールバックします。外部プロバイダー(Entra External ID、OIDC)では userName フィールドが OIDC サブジェクト識別子(ナビゲーションバーに表示するには不格好で意味のない、vs25QwNe1ZAHqlWK1Naw9dVEBe-TbF5tZEpb0XjAEZQ のような長い不透明な文字列)になるため、userName より email が優先されます。Power Pages は標準の OIDC クレーム(given_name、family_name、email)から firstName/lastName/email を設定します。Entra External ID のユーザーフローでは、返されるクレームに given_name/family_name がデフォルトで含まれないことがよくあります。名前を設定するには、「User attributes to collect」と「Application claims」/「User attributes to return as claims」の両方でユーザーフローの属性が選択されていることを確認してください(Phase 2.1 Entra External ID Step 3 を参照)。email クレームはほぼ常に出力されるため、名前が設定されない場合でもメールアドレスは確実に設定されます。getUserInitials() も同じ優先順位でフォールバックし、各フォールバック元の先頭文字を使用します。
  • 二要素認証:このスキルでは Power Pages 組み込みの 2FA はスキャフォールディングしません。SendCode/VerifyCode フローはサーバーレンダリングされ、SPA のエクスペリエンスには統合できないためです。MFA が必要な場合は、ID プロバイダーのレイヤー(Entra External ID の条件付きアクセス、B2C ユーザーフローの MFA、Auth0 Guardian、Okta Verify など)で構成します。IdP レベルの MFA は Power Pages から透過的に扱われ、IdP のブランドに沿ったエクスペリエンス内に留まります。
  • 招待ベースの登録:招待が有効になっている場合(REGISTRATION_MODE が Invitation-only または Both)、Dataverse に Invitation レコード(adx_invitation テーブル)を作成して招待コードを生成します。URL で使用する値は adx_invitationcode フィールドです。招待リンクは {site-url}/Account/Login/RedeemInvitation?invitation={code} の形式で共有します。Code-Site-Shell-Header スクリプトが、これを SPA の /redeem-invitation?invitation={code} ルートに自動的にリダイレクトします。引き換え後、招待はユーザーの連絡先に関連付けられます(単回引き換えの招待は引き換え済みとしてマークされ、グループ招待では引き換えた連絡先がコレクションで追跡されます)。利用規約への同意と外部ログインのフローでは、認証フローを通じて招待コードが保持されます。
  • Web ロールの割り当て:ユーザーには Power Pages 管理センターで適切な Web ロールを割り当てる必要があります
  • テーブルの権限:クライアント側の認証チェックは UX のためだけのものです。実際のデータセキュリティのために、/integrate-webapi を使用してサーバー側のテーブル権限を構成してください
  • ローカル開発:認証サービスには localhost でのテスト用にモックデータが含まれています。本番環境の前に削除するか無効にしてください
出力
  • ProfileRedirectEnabled サイト設定が作成されます
  • 完全な概要がユーザーに提示されます
  • デプロイが促されます(または、リマインダーを添えてスキップされます)
  • デプロイ後のガイダンスが提供されます

重要な注意事項

進捗の追跡

最初に TaskCreate を使用して、各フェーズを追跡します。

タスク説明
Phase 1前提条件の確認 — サイト、フレームワーク、デプロイ、Web ロールを確認します
Phase 2計画 — 要件を収集し、ユーザーの承認を得ます
Phase 3認証サービスの作成 — 認証サービス、型、フレームワークのフック/コンポーザブルを作成します
Phase 4認可ユーティリティの作成 — ロール確認関数とコンポーネントを作成します
Phase 5認証 UI の作成 — AuthButton コンポーネントとナビゲーションを統合します
Phase 6ロールベース UI の実装 — コンポーネントに認可パターンを適用します
Phase 7認証設定の検証 — ファイルの存在、ビルドの成功、認証 UI の表示を検証します
Phase 8レビューとデプロイ — サイト設定、概要、デプロイの確認を行います

フェーズが完了したら、TaskUpdate で各タスクを更新します。

主な判断ポイント
  • Phase 1.3:今すぐデプロイするか、停止するか?(認証設定の前にサイトをデプロイする必要があります)
  • Phase 1.4:今すぐ Web ロールを作成するか、スキップするか?(認可にはロールが必要です)
  • Phase 1.5:既存の認証ファイルを上書きするか、スキップするか?
  • Phase 2.1:どの認証機能を含めるか?(ログイン/ログアウト、ロールベース、または両方)
  • Phase 2.2:計画を承認するか、変更を依頼するか?
  • Phase 6.1:ロールベースのアクセスで保護するコンテンツ領域はどれか?
  • Phase 8.3:今すぐデプロイするか、後にするか?

Phase 1:前提条件の確認から開始してください

コメント