setup-auth について
setup-auth は、Power Pages SPA の認証とロールベース認可を設定するスキルです。複数の ID プロバイダーとサイトフレームワークに対応し、認証サービス、認可ユーティリティ、UI、ロール対応パターンを生成します。
主な機能
- 01複数の ID プロバイダーに対応
- 02ログイン・ログアウトサービスの作成
- 03ロール確認ユーティリティと UI の追加
- 04ローカルテスト用モックデータ
- 05サイトフレームワークに合う実装パターン
活用シーン
- 01Power Pages SPA へのサインイン追加
- 02Web ロールに応じた UI 表示
- 03ローカルでの認証フローのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill setup-authインストール後、Claude Code で/setup-authを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/auth-report.htmlreferences/authentication-reference.mdauthorization-reference.mdidp-provisioning-reference.mdscripts/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:前提条件の確認 — サイトの存在、フレームワークの検出、Web ロールの確認
- フェーズ 2:計画 — 認証要件を収集します(必要に応じて、IDP アプリ登録をナレッジファースト方式で設定します。デフォルトはガイド付きです。サポートされている場合は、プロバイダー独自の CLI を使って設定を代行できます:Okta/Auth0/Entra External ID)。その後、承認用の計画を提示します。
- フェーズ 3:認証サービスの作成 — ログイン/ログアウト機能と型宣言を備えた認証サービスを作成
- フェーズ 4:認可ユーティリティの作成 — ロールチェック関数とラッパーコンポーネントを作成
- フェーズ 5:認証 UI の作成 — ログイン/ログアウトボタンをナビゲーションに統合
- フェーズ 6:ロールベース UI の実装 — サイトコンポーネントにロールベースのパターンを適用
- フェーズ 7:認証設定の検証 — すべての認証ファイルの存在、ビルドの成功、認証 UI のレンダリングを検証
- フェーズ 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.yml | OIDC(Entra External ID、Okta、Auth0、汎用 OIDC、B2C はすべて OIDC 経由で処理されます) |
Authentication-SAML2-{Name}-AuthenticationType.sitesetting.yml | SAML2 |
Authentication-WsFederation-{Name}-AuthenticationType.sitesetting.yml | WS-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-savoriaUSER_FLOW_NAME_SUGGESTION={site-shortname}-signupsignin— 例:savoria-signupsignin
手順 1 の前にこれらをユーザーに表示し、すぐ参照できるようにします。
手順 1 — テナント
| 質問 | ヘッダー | 選択肢 |
|---|---|---|
| Microsoft Entra External ID テナントはすでにありますか?(これは通常の組織向け Entra ID テナントとは別の種類のテナントです。CIAM と呼ばれることもあります。) | テナント | はい — External ID テナントがあります、いいえ — 作成をサポートしてください(30 日間の無料試用版)、よくわかりません |
「いいえ」の場合、以下を表示します。
Entra External ID テナントを作成する手順:
- https://entra.microsoft.com/ を開きます
- テナントの所有者となるアカウントでサインインします
- 上部から テナントの管理 → 作成 をクリックします
- External(顧客向け) を選択します。Workforce ではありません。
- ドメインのプレフィックス(テナントのサブドメイン)を選びます。例:
contosoはcontoso.ciamlogin.comになります。これはすべてのログイン URL に表示されます。- 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 をそのまま含めて、手順を順番に表示します。
アプリを登録する手順:
https://entra.microsoft.com/ にアクセスし、External ID テナントにいることを確認します(右上のピッカーを使用)
アプリケーション → アプリの登録 → 新規登録
名前:
{APP_NAME_SUGGESTION}(または任意の名前)サポートされているアカウントの種類:**この組織ディレクトリのアカウントのみ(シングル テナント)**を選択します。Power Pages ではこの設定を推奨します。マルチテナント構成では、セキュリティ上、メールアドレスによるコンタクトのマッピングが強制的に無効になります。
リダイレクト URI:Webを選択し、次を正確に貼り付けます。
{REDIRECT_URI}(この値をそのままコピーしてください。この値と
RedirectUriサイト設定が一致しない場合、サインインに失敗し、AADSTS50011: The reply URL specified in the request does not matchが表示されます。)登録をクリックします
認証タブを開き、「暗黙的な許可およびハイブリッド フロー」でアクセス トークンとID トークンの両方をオンにして、保存をクリックします
API のアクセス許可タブを開き、{your tenant} に管理者の同意を与えますをクリックして、確認します
概要タブに戻り、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 も決まります。両者は一致させてください。ユーザーが明示的に異なる選択をしない限り、両方のデフォルトを「標準」にします)。
次に、以下を表示します。
ユーザーフローの作成手順:
- https://entra.microsoft.com/ にアクセスし、External ID テナントを開きます
- External Identities → User flows → New user flow
- Name:
{USER_FLOW_NAME_SUGGESTION}(または任意の名前 — 使用できるのは英字、数字、ハイフン、アンダースコアのみ)- サインイン用の Identity providers:Email with password(おすすめ — 多くのお客様にとって最もなじみやすい方法)または Email one-time passcode(パスワードレス)を選択します
- 収集する User attributes(サインアップフォームのフィールド):上記の選択に基づいて、以下を選択します。
- Standard / Standard + phone:☑ Email Address、☑ Given Name、☑ Surname{
, ☑ Phone Number(Standard + phone の場合)}- Email only:☑ Email Address
- クレームとして返す User attributes(ID トークン内):上記と同じ項目を選択します。これらの属性を使ってプロファイルを Dataverse の連絡先フィールドにマッピングします
- Create をクリックします
- 作成したユーザーフローを開き、Applications タブ → Add application → ステップ 2 で登録したアプリを選択 → Select
次に、以下を質問します。
| 質問 | 選択肢 |
|---|---|
作成した ユーザーフロー名を入力してください(例:{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 を参照):
| モード | Enabled | OpenRegistrationEnabled | InvitationEnabled | 動作 |
|---|---|---|---|---|
| オープン登録のみ | true | true | false | /registration への直接アクセスが機能します。招待リンクは 404 を返します。 |
| 招待のみ | true | false | true | /registration への直接アクセスは 404 を返します。ユーザーは招待リンク → /redeem-invitation → /registration?invitationCode=... の順にアクセスする必要があります。 |
| 両方 | true | true | true | どちらの経路も機能します。招待ではメールアドレスが事前入力され、直接登録は完全に開放されます。 |
| 登録無効 | 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つのセクションがあります。
- Account Details(上部に表示する読み取り専用の情報)— ユーザーのフルネーム(firstnameとlastnameを結合したもの)とメールアドレスのみを表示します。contactId、ロール、サインイン方法、プロバイダー名、最終ログイン日時、その他のアカウントメタデータは表示しないでください。このセクションは最小限にしてください。
- 編集フォーム(下部)— 編集できるのは以下のフィールドのみです。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 つすべてを満たす必要があります。
Authentication/Registration/TermsAgreementEnabled = true(これから作成するサイト設定)- Dataverse 環境に
msdynce_PortalPrivacyExtensionsソリューションがインストールされていること(IsGdprEnabledはポータルレベルの設定です)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 管理センターで実施する必要があります):
- アプリの登録 → {your app} → 認証に移動します
- フロントチャネルのログアウト URLフィールドまでスクロールします(「詳細設定」の下、「暗黙的な許可とハイブリッド フロー」のすぐ上にあります)
- **
{POST_LOGOUT_REDIRECT_URI}**を入力します — 上記の値と完全に一致させる必要があります- 保存します
これが必要な理由: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 |
ResponseType | OAuth レスポンスタイプ(code、id_token、code id_token) | code id_token |
ResponseMode | IdP がレスポンスを返す方法(form_post、query、fragment) | コードフローでは form_post |
RedirectUri | コールバック URL を上書きします | {site-url}/signin-{ProviderName-lowercased} |
PostLogoutRedirectUri | IdP でフェデレーションログアウトが完了した後のリダイレクト先 URL です。RPInitiatedLogout=true の場合は必須です。サーバーには RedirectUri の Authority から導出するフォールバックがありますが、別のフラグ(PostLogoutRedirectUriEnabled)により、フォールバックの使用前に明示的なサイト設定が存在することが求められます。明示的な値がない場合、IdP のログアウト URL にこのパラメーターが含まれず、ユーザーは行き場を失います。 | 未設定(サーバーのデフォルト。ただし、正しく設定するには上記のログアウトモードに関する質問を使用してください) |
RPInitiatedLogout | end_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 で確認してください。 | なし |
LoginClaimsMapping | RegistrationClaimsMapping と同じ形式です。毎回のログイン時に適用されます(連絡先フィールドを上書きします)。ユーザーがプロフィールに手動で加えた編集内容を上書きするため、慎重に使用してください。 | なし |
ExternalLogoutEnabled | ユーザーのログアウト時に IdP からサインアウトします(従来の OWIN サインアウトです。OIDC では RPInitiatedLogout を推奨します)。RPInitiatedLogout=true の場合は false に強制されます。 | false(サーバーのデフォルト) |
RegistrationEnabled | このプロバイダー経由で新規ユーザーが登録できるようにします | true |
AllowContactMappingWithEmail | email クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動リンクします。 デフォルトは false(常に新しい連絡先が作成されます)。⚠ マルチテナントの Entra External ID:サーバーはこれを強制的に無効にします(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。テナント間ではメールクレームを信頼できないためです。連絡先をリンクする場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ:true の場合、このプロバイダーに被害者のメールアドレスでサインインできる人は誰でも、被害者の連絡先にアクセスできます。メールアドレスを検証するプロバイダーであると信頼できる場合にのみ有効にしてください。 | false |
RequireUniqueEmail | 登録時にメールアドレスの一意性を必須にします | false |
UseTokenLifetime | セッション Cookie に IdP トークンの有効期間を使用します | 未設定 |
BackchannelTimeout | IdP へのバックチャネル HTTP 呼び出しのタイムアウト(例:00:01:00) | 00:01:00 |
RefreshOnIssuerKeyNotFound | 発行者キーが見つからない場合にプロバイダーのメタデータを更新します | デフォルト |
NonceEnabled | OIDC トークンの nonce 検証を有効にします | true |
NonceLifetime | OIDC nonce の有効期間(例:00:10:00) | 00:10:00 |
AcrValues | IdP に要求する Authentication Context Class Reference の値 | なし |
Prompt | OIDC の prompt パラメーター(login、consent、none)。セッションの有効期限が切れたときに再認証を強制するには、login を使用します。 | なし |
Resource | トークンリクエストの Resource パラメーター | なし |
EmailClaimIdentifier | ユーザーのメールアドレスとして使用するカスタムクレームタイプ | 標準のメールクレーム |
IssuerFilter | テナント間で発行者を照合するワイルドカードパターン(例:https://login.microsoftonline.com/*/v2.0)。マルチテナントアプリでは必須です。これがないと、ホームテナント以外のテナントでは発行者の検証に失敗します。 | なし |
UseUserInfoEndpointforClaims | UserInfo エンドポイントから追加のクレームを取得します | false |
UserInfoEndpoint | カスタム UserInfo エンドポイント URL(メタデータにない場合) | メタデータから取得 |
PasswordResetPolicyId | B2C/External ID のパスワードリセット用ユーザーフローポリシー名 | なし |
ProfileEditPolicyId | B2C/External ID のプロフィール編集用ユーザーフローポリシー名 | なし |
DefaultPolicyId | B2C/External ID のデフォルトのサインアップ/サインイン用ポリシー名 | なし |
TokenEndPointAuthenticatedMethod | トークンエンドポイントの認証方式(client_secret_post、client_secret_basic、private_key_jwt)。ソブリンクラウドで証明書ベースの認証を使用する場合は、private_key_jwt を使用します。 | client_secret_post |
AllowedDynamicAuthorizationParameters | 動的に引き渡すことができる OIDC パラメーターのカンマ区切りリスト | なし |
| SAML2 のオプション設定: |
| 設定 | 説明 | デフォルト |
|---|---|---|
AssertionConsumerServiceUrl | ACS 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 | 同じ形式です。ログインのたびに適用されます(連絡先フィールドを上書きします)。 | なし |
ExternalLogoutEnabled | SAML シングルログアウト(SLO)を有効にします | true |
RegistrationEnabled | このプロバイダー経由での新規ユーザー登録を許可します | true |
AllowContactMappingWithEmail | email クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動的にリンクします。 デフォルトは false です(常に新しい連絡先が作成されます)。⚠ マルチテナントの Entra External ID:サーバーによってこの設定は強制的に無効化されます(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。メールクレームはテナント間で信頼できません。連絡先をリンクする場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ: true の場合、このプロバイダーに被害者のメールアドレスでサインインできる人は、被害者の連絡先にアクセスできます。プロバイダーがメールアドレスを検証していると信頼できる場合にのみ有効にしてください。 | false |
AllowCreateNameIdPolicy | NameIdPolicy に AllowCreate を含めます | true |
DefaultSignatureAlgorithm | SAML リクエストの署名アルゴリズム | プロバイダーのデフォルト |
SigningCertificateFindType | リクエストの署名に使用する X509 証明書の検索の種類 | なし |
SigningCertificateFindValue | 証明書の検索値(例:サムプリント) | なし |
ExternalLogoutCertThumbprint | SLO 応答の署名に使用する証明書のサムプリント | なし |
SingleLogoutServiceRequestPath | SLO リクエストのカスタムパス | デフォルト |
SingleLogoutServiceResponsePath | SLO 応答のカスタムパス | デフォルト |
Comparison | AuthnContextComparison の種類(exact、minimum、maximum、better) | なし |
BackchannelTimeout | メタデータ取得のタイムアウト | 00:01:00 |
UseTokenLifetime | IdP のトークン有効期間をセッションに使用します | 未設定 |
EmailClaimIdentifier | ユーザーのメールアドレスに使用するカスタムクレームの種類 | 標準のメールクレーム |
IssuerFilter | マルチテナントの発行者照合に使用するワイルドカードパターン | なし |
WS-Federation のオプション設定:
| 設定 | 説明 | デフォルト |
|---|---|---|
Wreply | WS-Fed 応答の返信 URL | Wtrealm と同じ |
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 |
AllowContactMappingWithEmail | email クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動的に紐付けます。 デフォルトは false です(常に新しい連絡先が作成されます)。⚠ マルチテナント Entra External ID:サーバーがこの設定を強制的に無効にします(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。テナント間ではメールのクレームを信頼できません。連絡先を紐付ける場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ:true の場合、被害者のメールアドレスを使ってこのプロバイダーにサインインできる人は誰でも、被害者の連絡先にアクセスできます。メールアドレスを検証するプロバイダーであると信頼できる場合にのみ有効にしてください。 | false |
BackchannelTimeout | メタデータ取得のタイムアウト | 00:01:00 |
UseTokenLifetime | IdP のトークン有効期間をセッションに使用します | 未設定 |
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 |
AllowContactMappingWithEmail | email クレームと emailaddress1 を照合して、外部サインインを既存の Dataverse 連絡先に自動的に紐付けます。 デフォルトは false です(常に新しい連絡先が作成されます)。⚠ マルチテナント Entra External ID:サーバーがこの設定を強制的に無効にします(LoginController.cs:2578-2587 の BlockContactMappingSettingForMultitenantApp 機能フラグ)。テナント間ではメールのクレームを信頼できません。連絡先を紐付ける場合は、シングルテナントの Authority を使用してください。⚠ セキュリティ:true の場合、被害者のメールアドレスを使ってこのプロバイダーにサインインできる人は誰でも、被害者の連絡先にアクセスできます。メールアドレスを検証するプロバイダーであると信頼できる場合にのみ有効にしてください。 | false |
BackchannelTimeout | OAuth トークン交換のタイムアウト | 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/CookieDomain | Cookie のドメイン範囲 | 現在のドメイン |
Authentication/ApplicationCookie/CookiePath | Cookie のパス範囲 | / |
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/AzureADLoginEnabled | Azure 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 つのステップを実行して検証してから、次に進みます。
- アプリを登録する → Client ID を取得・検証します。
- Redirect URI を設定する →
<REDIRECT_URI>と完全に一致することを検証します。 - 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 時間)を使用します
- Phase 2.1.1 でユーザーが
- セッションタイムアウトからタイミングを算出します。間隔をハードコードしないでください。
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— 必要なロールをルートデータに指定できるCanActivateFnsrc/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(typeemail)、falseの場合はUsername(typetext)を使用します- 送信処理中はすべてのボタンを無効にします(ローカル用に
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-spotlight | PRIMARY_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フィールド(typeemail)を表示します。これはログイン識別子とメールアドレスの両方として使用されます。 - ユーザー名モード(
LocalLoginByEmail = false):Usernameフィールド(typetext)と、別のEmailフィールド(typeemail)を表示します。どちらも必須です。Username はログイン識別子として、Email は連絡先レコード用として必要です。
- メールアドレスモード(
PasswordとConfirm Passwordフィールド(どちらも typepassword)を含めます- 送信前に、クライアント側でパスワードが一致することを検証します
?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 フォーム(ログインなど)です。__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 パターンです — ユーザーがフィールドから離れたときにエラーが表示され、修正するとすぐに消えます。
実装パターン:
- フィールドごとに
touched状態を追跡する(ユーザーがどのフィールドを操作したか) - ブラー時(
onBlur): フィールドを touched としてマークし、検証を実行し、エラーを即座に表示する - 変更時(
onChange): フィールドがすでに touched である場合、再検証し、値が有効になった時点でエラーをクリアする。また、変更があればサーバーエラーもクリアする。 - 送信時: すべてのフィールドを touched としてマークし、すべてを検証し、すべてのエラーを一度に表示する
showError(field)ヘルパー: フィールドが touched である場合にのみエラーを返す
検証ルール:
| ページ | フィールド | 検証 |
|---|---|---|
| ログイン | メールアドレス | 必須 + 有効なメール形式 |
| ログイン | パスワード | 必須 |
| 登録 | メールアドレス | 必須 + 有効なメール形式 |
| 登録 | パスワード | 必須 + 8 文字以上 + 4 つのカテゴリ(小文字、大文字、数字、特殊文字)のうち少なくとも 3 つからの文字 |
| 登録 | パスワードの確認 | 必須 + パスワードと一致する必要がある |
| パスワードを忘れた場合 | メールアドレス | 必須 + 有効なメール形式 |
パスワード強度の検証は、デフォルトの Power Pages パスワードポリシー(EnforcePasswordPolicy)に一致します。サイト作成者が Authentication/UserManager/PasswordValidator/* サイト設定を通じてパスワードポリシーをカスタマイズした場合、クライアント側の検証もそれに一致させる必要があります。
5.1.5 利用規約ページを作成する(利用規約が有効な場合)
フェーズ 2 でユーザーが利用規約を有効にした場合、/terms SPA ページを作成します。このページは、ローカルサインイン、ローカル登録、外部プロバイダー(Entra External ID、OIDC、ソーシャル)のすべての認証フローに対応します。そのために、次の 2 つの補完的な仕組みを使います。
- ローカル認証フロー(
loginLocal、register):SPA のページ遷移を維持するためにfetch()を使用します。認証サービスはresponse.urlからサーバーのTermsAndConditionsへのリダイレクトを検出し、TermsRequiredErrorをスローします。ログイン/登録ページはこのエラーを捕捉し、/termsに遷移します。 - 外部認証フロー(
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 に既存の連絡先がない場合、サーバーはサーバー側でレンダリングされる招待フローに誘導します。
POST /Account/Login/ExternalLogin→ IdP →/signin-{ProviderName-lowercased}→/Account/Login/ExternalLoginCallbackExternalLoginCallbackは連絡先も招待も見つからないため、/Register?ReturnUrl=/(サーバー側でレンダリングされる RedeemInvitation フォーム)にリダイレクトします- ユーザーが招待コードを入力 → サーバーが検証 →
/Account/Login/Register?invitationCode=...(外部プロバイダーボタンがレンダリングされるローカルの Register Web Forms ページ)にリダイレクトします- ユーザーが 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 つあります:
- 招待リンクからの直接アクセス:ユーザーがメールのリンク
/Account/Login/RedeemInvitation?invitation={code}をクリック → ヘッダーのスクリプトが/redeem-invitation?invitation={code}にリダイレクトします。最初から URL に招待コードを含む、シンプルな SPA のエントリーポイントです。 - サーバー経由のリダイレクトパス(外部認証、連絡先なし、招待なし):ユーザーは招待リンクをクリックせずに、ログインページで「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)も更新する必要があります。
- マウント時に URL クエリパラメーターから
invitationCodeを読み取ります - 値がある場合は、情報バナーを表示します:「招待 {code} を引き換えるにはサインインしてください。サインイン後、招待がアカウントに紐付けられます。」
invitationCodeを新しい 5 番目のパラメーターとしてloginLocal()に渡します。認証サービスは/SignInPOST の 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 引数)を受け取れるようにします —/SignInURL に?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:__ExternalCookie が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 | 電話番号 | いいえ |
| 住所1 | address1_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)を呼び出します。成功した場合は、次の正確な順序で処理します。successMessage状態に "Profile updated." を設定します。- ローカルの
profileスナップショットを更新します(次回保存時にフォームの差分計算が正しく行われるようにします)。 applyContactUpdateLocally(payload)を呼び出します — 保存した名前フィールドをwindow.Microsoft.Dynamic365.Portal.Userに反映します。これはヘッダーを再描画するために必須です — これを行わないと、ページ全体の読み込みを発生させるルートに移動するまで、ヘッダーに保存前の名前が表示され続けます。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 が正常に解決された場合:
- インラインの
successMessagestate(「Profile updated.」)を設定します — フォーム上部の緑色のバナーで、閉じることができます useAuth().refresh()を呼び出します —window.Microsoft.Dynamic365.Portal.Userを再読み込みします。Portal.Userは初回ページ読み込み時のスナップショットであり、完全なページリロードが行われるまで新しい値を持たないことに注意してください。したがって、正しいパターンは、ローカルの UI state も更新することです(フォームフィールドはユーザーが入力したままの状態を維持し、ヘッダーのアバターは更新が反映されるまで一時的に古いイニシャルを表示する可能性があります)。この注意点をリファレンスに記載してください。- ページにとどまります — 自動リダイレクトは行いません。
5.2 ナビゲーションへの統合
サイトのナビゲーションコンポーネントを見つけて、認証ボタンを統合します:
- サイトのソースコード内でナビゲーション/ヘッダーコンポーネントを検索します
- AuthButton コンポーネントをインポートします
- 既存のハードコードされたサインインリンク(例:
<Link to="/login">Sign In</Link>や<a href="/signin">)を AuthButton コンポーネントに置き換えます。AuthButton はwindow.Microsoft.Dynamic365.Portal.Userを読み取り、認証されていない場合は「Sign In」を、認証されている場合はユーザー名 + アバター + 「Sign Out」ボタンを動的に表示します。ハードコードされたリンクは認証状態に反応しません。 - 複数のプロバイダーが設定されている場合: AuthButton の「Sign In」アクションは
/loginページへナビゲートする必要があります - 単一プロバイダーの場合: AuthButton の「Sign In」アクションは
login()を直接呼び出す必要があります - 統合後に、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 プロバイダーを使用する場合の例:
| ProviderName | CallbackPath | RedirectUri |
|---|---|---|
EntraExternalId | /signin-entraexternalid | https://contoso.powerappsportals.com/signin-entraexternalid |
OpenIdConnect_1(Okta) | /signin-openidconnect_1 | https://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"
なぜ
upnなのですか? ワークフォース Entra ID の OIDC エンドポイントは、デフォルトで v1.0 トークン(issuersts.windows.net/{tid}/)を使用し、デフォルトではupnクレームはワークフォースユーザーには常に含まれます。これはユーザープリンシパル名(例:[email protected])であり、ほとんどのワークフォースシナリオではメールアドレスとして機能します。これはワークフォース Entra ID(自動設定されるAzureADプロバイダー)にのみ適用されます。Entra External ID は適切な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-configurationAuthenticationType=Authorityと同じ値(ExternalLogin POST のプロバイダー識別子と一致する必要があります)ClientId= 手順のステップ 2 のEXTERNAL_ID_CLIENT_IDRedirectUri={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_MODE | Authentication/Registration/Enabled | Authentication/Registration/OpenRegistrationEnabled | Authentication/Registration/InvitationEnabled |
|---|---|---|---|
| オープン登録のみ | true | true | false |
| 招待制 | true | false | true |
| 両方 | true | true | true |
| 登録無効 | 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_MODE | CaptchaEnabled / 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-secret | Authentication/OpenIdConnect/{ProviderName}/ClientSecret | {prefix}_ProviderClientSecret |
facebook-app-secret | Authentication/OpenAuth/Facebook/AppSecret | {prefix}_FacebookAppSecret | |
google-client-secret | Authentication/OpenAuth/Google/ClientSecret | {prefix}_GoogleClientSecret | |
| Microsoft Account | microsoft-client-secret | Authentication/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/TermsAndConditionsHeadingAccount/Signin/TermsAndConditionsAgreementTextAccount/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:前提条件の確認から開始してください