クライアントAPI
PowerPortalsProは、どのシングルページアプリケーションでも呼び出せるJSONオーバーHTTPサーフェスを /api/* で公開しています。Blazor WebAssemblyクライアントはこれをバックで IPowerPortalsProService や IAuthServiceに使いますが、同じエンドポイントはReact、Vue、またはバニラJSフロントエンドでもサーバーと並行して(あるいはクロスオリジン)して同様に利用できます。このページは完全な参考資料です:すべてのクライアントが呼び出す可能なエンドポイントと、サンプル要求と応答を含んでいます。
これは誰宛てですか
Blazorアプリを構築し、
IPowerPortalsProServiceを通じてフレームワークを利用する場合、これらのエンドポイントを直接呼び出す必要はありません。クライアント実装が代わりに呼び出します。このページでは、非BlazorのSPAを書くチームやカスタムHTTPクライアントを同じサーバー上で配線するチームのための表面を記録しています。
エンドポイントの有効化
UsePowerPortalsProWebServer データエンドポイント(テーブルCRUD、FetchXML、メタデータ、ファイル、ローカライズ、管理者)を配線します。 MapAuthEndpoints<TUser> SPA向きの認証サーフェスを配線します。ホストがSPAからクッキー認証を必要とする場合は明示的に呼び出してください。
// Program.cs — サーバーパイプライン
app.UsePowerPortalsProWebServer();
app.MapAuthEndpoints<PortalUser>();
両方の通話は機能を使わないときはノーオペス(無操作)なので、ホストがどのインタラクティビティモードを使っていても Program.cs に一度だけ配線してください。
ルートタイプ — 単一の真実の情報源
PowerPortalsPro.Web.Common.Routes すべての端点パスを強型付きプロパティとして公開します。インボックスクライアントもC#ベースの外部SPAもこれらの定数を参照すべきであり、手書き文字列ではなく、サーバー側のリネームはランタイム404ではなくコンパイルエラーとして現れます。JavaScriptやTypeScriptクライアントはもちろんパスをインライン化する必要がありますが、C#側 Routes パスの標準的な参照として使われています。
// どこでも、PowerPortalsPro.Web.Clientと外部のC# SPAの両方で。
var loginUrl = Routes.Api.Auth.Login; // 「/api/auth/login」
var meUrl = Routes.Api.Auth.Me; // 「/api/auth/me」
var createUrl = Routes.Api.Tables.GetCreateRoute("contact");
var fetchUrl = Routes.Api.GetRetrieveMultipleRoute(fetchXml);
Routes.Api データおよび管理エンドポイントをカバーします。 Routes.Api.Auth はサインイン/サインアップ、 Routes.Api.Auth.Manage をカバーしています。サインインしたユーザーのアカウント管理業務をカバーします。
認証モデル — トークンではなくクッキー
認証はブラウザクッキーベースです。JWT発行のステップはありません。SPAが/api/auth/loginを呼び出し、サーバーは応答に対して.AspNetCore.Identity.Applicationクッキーを設定し、ブラウザはその後のすべてのリクエスト(/api/table/*でのデータ呼び出しを含む)にそれをアタッチします。JavaScriptからは、すべての通話(またはaxios.defaults.withCredentials = true)でfetch(..., { credentials: 'include' })を意味します。JavaScriptがなければクッキーはドロップされ、サーバーは401を返します。以下のすべての例がそれを含めています。
// React / Vue / plain fetch — 認証情報:'include' が必須であるため
// ブラウザは/api/auth/loginによって発行された認証クッキーを送信・保存します。
const me = await fetch('/api/auth/me', { credentials: 'include' })
.then(r => r.json());
if (me.isAuthenticated) {
console.log(me.userName, me.roles);
}
クロスオリジンSPA
SPAとサーバーが異なる発信元にある場合、サーバーは
Access-Control-Allow-Origin: <spa-origin>(*ではなく)とAccess-Control-Allow-Credentials: trueを送信し、SPAはcredentials: 'include'を使わなければなりません。同じソースホスティング(APIと同じサイトからSPAを提供する)はこの問題を完全に回避します。
記録の形状
行を読み書きするすべてのデータエンドポイントは同じエンベロープ TableRecord 交換します。 properties は、列論理名のマップ→型付き値オブジェクトのマップであり、その $type 識別子が列の種類を識別します。 permissions ビットフラグマスク(Read 1、Create 2、Write 4、Delete 8、Append 16、AppendTo 32)で、現在のユーザーが行で何をするかを記述します。書き込み時は、変更する列だけを送ればいいです。
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"tableName": "account",
"permissions": 15,
"properties": {
"name": { "$type": 14, "value": "Acme Corporation" },
"revenue": { "$type": 8, "value": 5000000, "formattedValue": "$5,000,000.00" },
"createdon": { "$type": 2, "value": "2026-05-15T10:30:00Z", "formattedValue": "May 15, 2026 10:30 AM" },
"primarycontactid": { "$type": 6, "value": "9f8e7d6c-...", "name": "Jane Doe", "tableName": "contact" }
},
"formattedValues": { "revenue": "$5,000,000.00" },
"currency": { "isoCode": "USD", "symbol": "$", "precision": 2 }
}
$type値はDataverse属性の種類を反映しています:0 ブール、2 DateTime、3 Decimal、4 Double、5 Integer、6 Lookup、8 Money、11 Choice、14 String、15 UniqueIdentifier、40 MultiSelectChoice、41 File、42 画像。ルックアップはname+tableNameを加え、数値、マネー、日付の値はDataverse形式のformattedValueを持ちます。
エラー応答
失敗した通話はRFC 9457 application/problem+jsonを返します。インボックスクライアントの実装は、問題の詳細から元のCLR例外タイプをリハイドレートするため、サーバー側はクライアント側で同じ例外として表面をスローします。non-.NET SPAでは、 type / title / detail / status フィールドが標準ハンドルです。
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"status": 400,
"detail": "The record could not be saved because a required column was missing."
}
エンドポイント参照
以下の各エンドポイントは、メソッドとパス、その役割、サンプルリクエスト(ブラウザ fetch コールとしての)、およびサンプルレスポンスを示しています。 {braces} のパスセグメントはプレースホルダーです。特に記載がない限り、2xxの応答が成功例であり、失敗は problem+jsonとして返ってきます。
記録とCRUD
単一レコードの作成、読み取り、更新、削除に加え、任意のFetchXMLクエリやトランザクションバッチも可能です。 UsePowerPortalsProWebServerに裏付けられ、すべての読み書きは消費者の ITablePermissionHandler / ITableRecordPermissionHandler インターセプターおよび登録された IFetchXmlBuilderInterceptor を適用します。
POST /api/table/{tableLogicalName}
名前付きテーブルに新しい行を作成します。設定する列を載せた TableRecord を送信します。応答は新しいレコードのIDを返します。
要望
const res = await fetch('/api/table/account', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tableName: 'account',
properties: {
name: { $type: 14, value: 'Acme Corporation' },
revenue: { $type: 8, value: 5000000 }
}
})
});
const { id } = await res.json();
反応
{
"responseName": "CreateResponse",
"outputParameters": {},
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
GET /api/table/{tableLogicalName}/{recordId}
IDで1行読み取る。オプションの ?columns= クエリパラメータ(カンマ区切られた論理名)は射影を絞り込みます — テーブルのデフォルトカラムセットを取得するために省略してください。
要望
const record = await fetch(
'/api/table/account/a1b2c3d4-e5f6-7890-abcd-ef1234567890?columns=name,revenue',
{ credentials: 'include' }
).then(r => r.json());
反応
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"tableName": "account",
"permissions": 15,
"properties": {
"name": { "$type": 14, "value": "Acme Corporation" },
"revenue": { "$type": 8, "value": 5000000, "formattedValue": "$5,000,000.00" }
},
"formattedValues": { "revenue": "$5,000,000.00" },
"currency": { "isoCode": "USD", "symbol": "$", "precision": 2 }
}
PATCH /api/table/{tableLogicalName}/{recordId}
既存の行を更新します。 properties にある列だけが書き込まれているので、変更した値だけを送信してください。
要望
await fetch('/api/table/account/a1b2c3d4-e5f6-7890-abcd-ef1234567890', {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tableName: 'account',
properties: { revenue: { $type: 8, value: 6500000 } }
})
});
反応
{ "responseName": "UpdateResponse", "outputParameters": {} }
DELETE /api/table/{tableLogicalName}/{recordId}
IDで行を削除します。
要望
await fetch('/api/table/account/a1b2c3d4-e5f6-7890-abcd-ef1234567890', {
method: 'DELETE',
credentials: 'include'
});
反応
{ "responseName": "DeleteResponse", "outputParameters": {} }
GET /api/retrieveMultiple?fetchXml=…
任意のFetchXMLクエリを実行し、対応する行とページング情報を返します。FetchXMLを fetchXml クエリ文字列にC#からエンコードしてください。 Routes.Api.GetRetrieveMultipleRoute(fetchXml) これを代わりに行います。
要望
const fetchXml = `<fetch><entity name="account">
<attribute name="name" /><attribute name="revenue" />
<order attribute="name" />
</entity></fetch>`;
const result = await fetch(
'/api/retrieveMultiple?fetchXml=' + encodeURIComponent(fetchXml),
{ credentials: 'include' }
).then(r => r.json());
反応
{
"pagingInfo": { "totalRecordCount": 42, "pagingCookie": "<cookie page=…>", "pageNumber": 1 },
"tableRecords": [
{
"id": "a1b2c3d4-…",
"tableName": "account",
"permissions": 1,
"properties": { "name": { "$type": 14, "value": "Acme Corporation" } },
"formattedValues": {}
}
]
}
POST /api/executeMultiple?returnResponses=true|false
単一のデータベーストランザクションで異種リクエスト(作成/更新/削除/アソシエイト/離脱)を一括実行し、いずれかが失敗するとバッチ全体がロールバックされます。本体は$typeによって区別されたOrganizationRequestオブジェクトのJSON配列です。リクエストごとのレスポンスリスト作成をスキップするには?returnResponses=falseパスしてください。
要望
await fetch('/api/executeMultiple?returnResponses=true', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([
{ $type: 'CreateRequest', record: { tableName: 'contact',
properties: { lastname: { $type: 14, value: 'Doe' } } } },
{ $type: 'DeleteRequest', record: { tableName: 'account', id: 'old-guid' } }
])
});
反応
[
{ "$type": "CreateResponse", "responseName": "CreateResponse", "outputParameters": {}, "id": "new-guid" },
{ "$type": "DeleteResponse", "responseName": "DeleteResponse", "outputParameters": {} }
]
グリッドとチャート
サーバー構成クエリエンドポイント。クライアントにFetchXMLをビルドさせる代わりに、ビューID(または自分のFetchXML)と検索・ソート・ページングを渡し、サーバーが列を解決し、権限を適用しクエリを実行します。これはMainGridやチャートコンポーネントが使う単一真実の経路と同じです。
POST /api/grids/data
グリッドデータのページを読み込みます。 viewId またはご自身のご fetchXml、さらにオプションで searchText、 sorts、ページング、カラムフィルターも用意してください。レスポンスは行、解決された列の定義、呼び出し元のテーブル権限マスクを伝えます。
要望
const page = await fetch('/api/grids/data', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
viewId: '00000000-0000-0000-0000-000000000001',
searchText: 'acme',
sorts: [{ columnName: 'name', descending: false }],
pageNumber: 1,
pageSize: 50
})
}).then(r => r.json());
反応
{
"pagingInfo": { "totalRecordCount": 2, "pageNumber": 1 },
"tableRecords": [ { "id": "a1b2c3d4-…", "tableName": "account",
"properties": { "name": { "$type": 14, "value": "Acme Corporation" } } } ],
"columns": [
{ "columnName": "name", "displayName": "Name", "type": 14, "isSortable": true, "isPrimaryName": true }
],
"tablePermissions": 15
}
POST /api/charts/data
チャート作成コンポーネント向けに集約されたチャートデータを読み込みます。集計設定、 viewId、生の fetchXmlに加え、ラベル/値/系列の列マッピングを受け付けます。Chart.jsスタイルのラベルとデータセットを返します。
要望
const chart = await fetch('/api/charts/data', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
viewId: '00000000-0000-0000-0000-000000000002',
labelColumn: 'statuscode',
valueColumn: 'count',
singleSeriesLabel: 'Accounts by Status'
})
}).then(r => r.json());
反応
{
"data": {
"labels": ["Active", "Inactive"],
"datasets": [ { "label": "Accounts by Status", "data": [ { "value": 25 }, { "value": 8 } ] } ]
}
}
メタデータと権限
テーブルやビューメタデータ、現在のユーザーの権限マスク、組織全体の設定の読み取り専用ルックアップ。すべてサーバーサイドでキャッシュされているため、繰り返し通話は安価です。
GET /api/tableMetadata/{tableLogicalName}
テーブルのメタデータ、すなわち型、ラベル、制約付きカラム、プライマリid/名前/画像カラム、リレーションシップを返します。
要望
const meta = await fetch('/api/tableMetadata/account', { credentials: 'include' })
.then(r => r.json());
反応
{
"tableName": "account",
"objectTypeCode": 1,
"primaryIdColumn": "accountid",
"primaryNameColumn": "name",
"isIntersect": false,
"columns": [ { "$type": 14, "columnName": "name", "displayName": "Name", "maxLength": 160 } ],
"oneToMany": [ { "relationshipName": "account_contacts", "referencingEntity": "contact" } ],
"manyToOne": [],
"manyToMany": []
}
GET /api/permissions/table/{tableLogicalName}
現在のユーザーのテーブルの統合 TableSecurityPermission マスクを単一の整数として返します(ビットフラグ:Read 1, Create 2, write 4, Delete 8, Append 16, AppendTo 32)。
要望
const mask = await fetch('/api/permissions/table/account', { credentials: 'include' })
.then(r => r.json());
// 15 === Read(1) |Create(2) |Write(4) |削除(8)
反応
15
GET /api/viewMetadata/{viewId}
保存されたビューのメタデータをGUID(FetchXML、レイアウトカラム、ビューフラグ)で返します。このルートにはGUIDが必要で、以下の全ビュールートと区別されています。
要望
const view = await fetch('/api/viewMetadata/00000000-0000-0000-0000-000000000001',
{ credentials: 'include' }).then(r => r.json());
反応
{
"id": "00000000-0000-0000-0000-000000000001",
"name": "All Accounts",
"tableName": "account",
"isDefault": true,
"fetchXml": "<fetch>…</fetch>",
"columns": [ { "columnName": "name", "displayName": "Name", "width": 300 } ]
}
GET /api/viewMetadata/{tableLogicalName}
テーブルの保存済みすべてのビューを返します。by-idルートと共通の /api/viewMetadata/ プレフィックス — 非GUIDセグメント(テーブルの論理名)がここに着きます。
要望
const views = await fetch('/api/viewMetadata/account', { credentials: 'include' })
.then(r => r.json());
反応
[
{ "id": "0000…0001", "name": "All Accounts", "isDefault": true, "tableName": "account" },
{ "id": "0000…0002", "name": "Active Accounts", "isDefault": false, "tableName": "account" }
]
GET /api/organizationSettings
Dataverseの組織レコードから得た組織全体の設定を返します:デフォルトの通貨、ブロックされたファイル拡張子リスト、最大アップロードサイズ(バイト単位)。
要望
const settings = await fetch('/api/organizationSettings', { credentials: 'include' })
.then(r => r.json());
反応
{
"defaultCurrency": { "isoCode": "USD", "symbol": "$", "precision": 2 },
"blockedFileExtensions": ["exe", "bat", "js"],
"maxUploadFileSizeInBytes": 10485760
}
ファイル
ファイルと画像の列の内容を読みます。バイナリペイロードはJSON内でbase64エンコードで返されます。 includeData フラグは、バイトを転送せずにメタデータのみを取得することを可能にします(例:ダウンロードリストのレンダリング)。
GET /api/files/{tableLogicalName}/{recordId}/{columnName}?includeData=…
1つのレコードでファイル/画像列のメタデータを返します。 ?includeData=true ではbase64の内容が埋め込まれますが、 false では名前とサイズのみが戻ってきます。
要望
const file = await fetch(
'/api/files/account/a1b2c3d4-…/new_attachment?includeData=true',
{ credentials: 'include' }
).then(r => r.json());
反応
{
"fileName": "contract.pdf",
"fileSizeInBytes": 245678,
"fileData": "JVBERi0xLjQKJeLjz9M…"
}
POST /api/files/{tableLogicalName}/{columnName}/batch?includeData=…
同じテーブル/カラムの多くのレコードのメタデータ(およびオプションで内容)を一度の往復で取得します — 本体はレコードGUIDのJSON配列です。FileGridのダウンロード選択で使われ、クライアントがN回の個別呼び出しを発生させないようにします。
要望
const files = await fetch('/api/files/account/new_attachment/batch?includeData=false', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(['a1b2c3d4-…', 'b2c3d4e5-…'])
}).then(r => r.json());
反応
[
{ "fileName": "contract.pdf", "fileSizeInBytes": 245678, "fileData": null },
{ "fileName": "invoice.docx", "fileSizeInBytes": 89456, "fileData": null }
]
POST /api/files/createFileArchive
サーバー側でレコードセットのファイルカラム値をZip化します。デフォルトで生の application/zip ストリームを返すか、 responseFormat: 1 の場合はbase64アーカイブを含むJSONエンベロープを返します。POST(GETではなく)で、大きなIDリストがURL長の制限に耐えないようにします。
要望
// デフォルト:生のアプリケーション/zipストリーム。JSONエンベロープの場合はPass responseFormat: 1。
const blob = await fetch('/api/files/createFileArchive', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tableName: 'account',
columnName: 'new_attachment',
recordIds: ['a1b2c3d4-…', 'b2c3d4e5-…']
})
}).then(r => r.blob());
反応
// responseFormat: デフォルトのバイナリストリームの代わりに1(Json)を
{
"fileName": "account-files.zip",
"contentType": "application/zip",
"data": "UEsDBBQAAAAIAP2t…"
}
局在束
非Blazorのフロントエンド向けのローカライズされた文字列。 /api/localizedStrings ルートは文化のツリー全体を返します。 /localizations/* ルートはサムプリント付きで不変キャッシュされたバンドル(デフォルト、テーブルごと、ビューごと)を提供し、効率的な増分ロードを実現します。これらは公開されており、認証クッキーを必要としません。
GET /api/localizedStrings/{culture}
文化のローカライズされた文字列ツリー全体をネストオブジェクトとして返します — フレームワーク文字列、アプリのオーバーライド、テーブルラベル、選択ラベルなどです。
要望
const strings = await fetch('/api/localizedStrings/fr-fr', { credentials: 'include' })
.then(r => r.json());
反応
{
"app": { "navigation": { "home": "Accueil" } },
"tables": { "account": { "columns": { "name": { "label": "Nom du compte" } } } }
}
GET /localizations/version
ローカライゼーションマニフェストを返します — サポートされているローカリストのリストだけです。キャッシュなしで提供されているので、次のページロード時に新しいリリースが検出されます。公衆。
要望
const manifest = await fetch('/localizations/version').then(r => r.json());
反応
{ "supportedLocales": ["en-us", "fr-fr", "de-de"] }
GET /localizations/{locale}/thumbprints
1つのローカ(デフォルトのバンドルと読み込まれたすべてのテーブルとビュー)のコンテンツのサムプリントを返します。クライアントはこれらを取得し、サムプリントが変わったバンドルのみを要求します。公衆。
要望
const thumbs = await fetch('/localizations/fr-fr/thumbprints').then(r => r.json());
反応
{
"bundle": "a3f5e8c2d",
"tables": { "account": "b7f2d9e1a", "contact": "c4f8a1b3e" },
"views": { "550e8400e29b41d4a716446655440000": "e2f4b8d1c" }
}
GET /localizations/default/{filename} · /tables/{tableName}/{filename} · /views/{viewId}/{filename}
3つのバンドルファミリーは、デフォルト(クロスカッティング文字列)、per-table(テーブルの文字列とその列が参照するグローバル選択)、およびper-viewです。ファイル名は {locale}.{thumbprint}.json されており、それぞれ public, immutable, max-age=31536000に配信されるため、安定したサムプリントはキャッシュヒットが保証されます。公衆。
要望
// ファイル名は「{locale}」です。{thumbprint}.json'、不変 + キャッシュ-フォーエバーに提供されます。
const bundle = await fetch('/localizations/tables/account/fr-fr.b7f2d9e1a.json')
.then(r => r.json());
反応
{
"tables": { "account": { "label": "Compte", "columns": { "name": { "label": "Nom du compte" } } } },
"choices": { "account_industrycode": { "1": "Fabrication", "2": "Services" } }
}
文化
文化クッキーを書き込むことでブラウザのアクティブな文化を切り替えます。
GET /Culture/{culture}?redirectUri=…
文化クッキーと302リダイレクトを redirectUriに設定します。取得するのではなく、ページ全体読み込みでナビゲートして、 Set-Cookie とリダイレクトが有効になります。公衆。
要望
// Set-Cookie+リダイレクトが尊重されるように、全ページナビゲート。
window.location.href = '/Culture/fr-fr?redirectUri=' + encodeURIComponent('/dashboard');
反応
// 302 発見→場所:/dashboard
// セットクッキー: 。AspNetCore.Culture=c=fr-fr|uic=fr-fr
管理者 — キャッシュ管理
サーバーキャッシュの検査と無効化。3つのエンドポイントはすべて [Authorize(Roles = "SystemAdmin")] でゲートされており、サインインした管理者のみが呼びかけることができます。
GET /api/caches
登録されたすべてのサーバーサイドキャッシュの名前を一覧にします。SystemAdminの役割が必要です。
要望
const names = await fetch('/api/caches', { credentials: 'include' }).then(r => r.json());
反応
["TableMetadataCache", "ViewMetadataCache", "CurrencyCache"]
POST /api/caches/clear
すべてのサーバー側キャッシュをクリアし、キャッシュごとに成功したかどうか、そして所要時間などの結果を返します。SystemAdminの役割が必要です。
要望
const results = await fetch('/api/caches/clear', { method: 'POST', credentials: 'include' })
.then(r => r.json());
反応
[
{ "name": "TableMetadataCache", "succeeded": true, "error": null, "elapsedMs": 45 },
{ "name": "ViewMetadataCache", "succeeded": false, "error": "Timed out", "elapsedMs": 5000 }
]
POST /api/caches/{cacheName}/clear
名前はリストエンドポイントから来る単一の名前付きキャッシュをクリアします。404の名前のキャッシュが登録されていなければ返されます。SystemAdminの役割が必要です。
要望
const result = await fetch('/api/caches/TableMetadataCache/clear',
{ method: 'POST', credentials: 'include' }).then(r => r.json());
反応
{ "name": "TableMetadataCache", "succeeded": true, "error": null, "elapsedMs": 132 }
認証
サインイン、サインアップ、アカウントライフサイクル、 MapAuthEndpoints<TUser>によるサポート。クッキーベース:サインインが成功すると ASP.NET Core Identityアプリケーションクッキーが設定され、その後の呼び出しごとにそれを返して認証します。ほとんどのシステムは、ステータスコードを通じて結果を伝えるのではなく、HTTP 200で result 列挙を返します。
POST /api/auth/login
メール+パスワードでサインインします。 result 列挙は、成功、必須の第二要素、悪い資格情報、未確認のメール、ロックアウトを区別します。成功すると、レスポンスに対して認証クッキーが設定されます。
要望
const { result } = await fetch('/api/auth/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com', password: 'P@ssw0rd!', rememberMe: true })
}).then(r => r.json());
// 結果:0 成功 ·1 RequiresTwoFactor ·2 InvalidCredentials ·3 EmailNotConfirmed(確認されていない)・4 ロックアウト
反応
{ "result": 0 }
POST /api/auth/login/2fa
認証ツール(またはリカバリー)コードを提出することで、返 RequiresTwoFactor されたサインインを完了します。 rememberMachine 、このブラウザでの今後のサインイン時に2つ目の要素をスキップするように、信頼されたブラウザクッキーを設定します。
要望
const { success } = await fetch('/api/auth/login/2fa', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: '123456', rememberMachine: false })
}).then(r => r.json());
反応
{ "success": true }
POST /api/auth/logout
認証クッキーをクリアし、セッションを終了します。
要望
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
反応
// 200 OK — 遺体なし。返信すると認証クッキーはクリアされます。
POST /api/auth/register
新しいローカルアカウントを作成します。設定によっては、確認メール送信、即時サインイン、または既存のメールとの競合のいずれかが発生します。弱いパスワードやその他の検証失敗は400 problem+jsonと表示されます。
要望
const { result } = await fetch('/api/auth/register', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'newuser@example.com', password: 'P@ssw0rd!' })
}).then(r => r.json());
// 結果:0 確認メール送信 ·1 サインイン済み ·2 メールAlreadyInUse
反応
{ "result": 0 }
POST /api/auth/forgot-password
リセットリンクをメールで送ることでパスワードリセットを開始します。必ず200を返すが、そのアドレスの存在は確認せず、登録済みメールの調査には使えません。
要望
await fetch('/api/auth/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com' })
});
反応
// 200 OK — 住所が存在するかどうかに関わらず(列挙可能)は存在しない。
POST /api/auth/reset-password
メールのトークンと新しいパスワードを使ってリセットを完了します。 result 成功、無効または期限切れトークン、却下されたパスワード(検証メッセージが errorsにある)を区別します。
要望
const res = await fetch('/api/auth/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com', code: '<token-from-email>', newPassword: 'N3wP@ss!' })
}).then(r => r.json());
// 結果:0 成功 ·1 InvalidOrExpiredToken ·2 InvalidPassword
反応
{ "result": 0, "errors": [] }
POST /api/auth/confirm-email
確認リンクからユーザーIDとトークンを使って新たに登録されたメールを確認します。
要望
const { success } = await fetch('/api/auth/confirm-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: '550e8400-…', code: '<token-from-email>' })
}).then(r => r.json());
反応
{ "success": true }
POST /api/auth/resend-email-confirmation
確認メールのリンクを再送信します。forgot-passwordのように、登録済みアドレスの漏洩を避けるために、必ず200を返します。
要望
await fetch('/api/auth/resend-email-confirmation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com' })
});
反応
// 200 OK — 遺体なし(列挙可能)。
GET /api/auth/options
サインイン設定を1回の匿名通話で返します: localAccountsEnabled (ポータルがローカルのユーザー名/パスワードアカウントを受け入れているか否)と、サインインボタン用の設定済み外部(OAuth)プロバイダー(スキーム名と表示名)を含みます。
要望
const options = await fetch('/api/auth/options').then(r => r.json());
反応
{
"localAccountsEnabled": true,
"externalProviders": [
{ "name": "Microsoft", "displayName": "Microsoft" },
{ "name": "Google", "displayName": "Sign in with Google" },
{ "name": "Facebook", "displayName": "Facebook" }
]
}
GET /api/auth/external-login?provider=…&returnUrl=…
プロバイダーのOAuthフローをスタートさせます。プロバイダーに302チャレンジを返すので、ブラウザでそれにアクセスし(取得しないでください)、SPAは window.location.hrefを設定するべきです。
要望
// 全ページナビゲーション — 取ってこないでください — ブラウザはOAuth 302チェーンに従います。
window.location.href =
'/api/auth/external-login?provider=Microsoft&returnUrl=' + encodeURIComponent('/dashboard');
反応
// 302 外部プロバイダーのサインインページ(および相関クッキー)→見つかります。
GET /api/auth/external-login/pending
OAuthコールバック後、機内外部ログインのスナップショットを抽出します:プロバイダー名、アイデンティティの主張、そしてメールが複数のポータルIDと一致した場合は、選択可能な候補リストです。保留中のログインがないと204ページを返します。
要望
const res = await fetch('/api/auth/external-login/pending', { credentials: 'include' });
const pending = res.status === 204 ? null : await res.json();
反応
{
"loginProvider": "Microsoft",
"providerDisplayName": "Microsoft",
"identityEmail": "user@company.com",
"requiresChoice": false,
"candidates": []
}
POST /api/auth/external-login/confirm
新しいアカウントの初回外部サインインを、アソシエイトにメールアドレスを確認することで完成します。サインイン、確認メール、保留なし、失敗に解決します。
要望
const { result } = await fetch('/api/auth/external-login/confirm', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@company.com' })
}).then(r => r.json());
// 結果:0 サインイン ·1件確認メール送信・2 NoPendingExternalLogin ·3 失敗
反応
{ "result": 0, "errors": [] }
POST /api/auth/external-login/select
複数のポータルIDと一致した際、どちらか(ContactまたはSystemUser)でサインインすることで外部サインインを完了します。
要望
const { result } = await fetch('/api/auth/external-login/select', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ kind: 1 }) // 0 接触 ·1 SystemUser
}).then(r => r.json());
反応
{ "result": 0, "errors": [] }
GET /api/auth/me
現在の校長のスナップショット — ID、名前、メールアドレス、役割、バックアップテーブル(contact 対 systemuser)、さらに任意の兄弟姉妹の識別名。クッキーが存在しない場合、401ではなく匿名の形状(isAuthenticated: false)を返すため、SPAはステータスコードで分岐せずに最初のペイントで呼び出せます。
要望
const me = await fetch('/api/auth/me', { credentials: 'include' }).then(r => r.json());
反応
{
"isAuthenticated": true,
"userId": "550e8400-…",
"userName": "user@example.com",
"email": "user@example.com",
"roles": ["Member"],
"tableName": "contact",
"altIdentityTableName": "systemuser",
"altIdentityUserId": "660e8400-…"
}
POST /api/auth/switch-identity
現在のクッキーをユーザーの別の兄弟身分(Contact↔SystemUserペアリングは /api/auth/meに出現)に交換します。JSONの本体は空のオブジェクトです。
要望
const { result } = await fetch('/api/auth/switch-identity', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: '{}'
}).then(r => r.json());
// 結果:0 入れ替わり ·1 NoAltIdentity ·2 AltIdentityNotFound(見つからなかった)·3 認証されていない
反応
{ "result": 0 }
アカウント管理
サインインしたユーザーのセルフサービス操作は、プロフィール、パスワード、メール、二要素認証、リンクされた外部ログイン、個人情報など、 /api/auth/manage/* で行われます。すべて認証セッションが必要で、フレームワークのクラシックな /Account/Manage Razorページをミラーリングします。
GET /api/auth/manage/profile
Dataverseの連絡先から読んだユーザーのプロフィール(名前、モバイル端末、メールアドレス)と、Identityステータスフラグ(メール確認済み、パスワード設定、2段階認証有効、読み取り専用)を返します。
要望
const profile = await fetch('/api/auth/manage/profile', { credentials: 'include' })
.then(r => r.json());
反応
{
"firstName": "John", "lastName": "Doe", "mobilePhone": "+1-555-0123",
"email": "john.doe@example.com",
"isEmailConfirmed": true, "hasPassword": true, "isTwoFactorEnabled": false, "isReadOnly": false
}
POST /api/auth/manage/profile
リンク先の連絡先で氏名・名字と携帯電話番号を更新します。成功すると200を返す。SystemUserバックアップのIDは読み取り専用で、403を取得します。
要望
await fetch('/api/auth/manage/profile', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ firstName: 'Jane', lastName: 'Smith', mobilePhone: '+1-555-9876' })
});
反応
// 200 OK — 遺体なし。(読み取り専用のSystemUserバックアップIDには403。)
POST /api/auth/manage/password/set
ローカルパスワードを持たないアカウント(例:外部ログイン専用アカウント)に追加します。もし何かあれば、 errorsに確認メッセージが返ってきます。
要望
const res = await fetch('/api/auth/manage/password/set', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ newPassword: 'N3wP@ss!' })
}).then(r => r.json());
反応
{ "success": true, "errors": [] }
POST /api/auth/manage/password/change
ローカルパスワードを変更する;現在のパスワードが必要です。 result 成功、間違った現在のパスワード、拒否された新しいパスワードを区別します。
要望
const { result } = await fetch('/api/auth/manage/password/change', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ oldPassword: 'Old!', newPassword: 'N3wP@ss!' })
}).then(r => r.json());
// 結果:0 成功 ·1 誤った旧パスワード ·2 InvalidPassword
反応
{ "result": 0, "errors": [] }
POST /api/auth/manage/email/change
新しい住所に確認リンクを送ることでメール変更を開始します。この変更は、そのリンクをたどった場合にのみ有効です。
要望
const { result } = await fetch('/api/auth/manage/email/change', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ newEmail: 'new@example.com' })
}).then(r => r.json());
// 結果:0 確認メール送信 ·1 SameAsCurrentEmail
反応
{ "result": 0 }
POST /api/auth/manage/email/send-confirmation
ユーザーの現在のメールアドレスの確認リンクを再送信します。 sent メールがすでに確認されている場合は誤りです。
要望
const { sent } = await fetch('/api/auth/manage/email/send-confirmation',
{ method: 'POST', credentials: 'include' }).then(r => r.json());
反応
{ "sent": true }
GET /api/auth/manage/2fa
2FAの状態を返します — 認証ソフトが登録されているか、2FAが有効か、このブラウザが記憶されているか、そして残っている回復コードの数です。
要望
const status = await fetch('/api/auth/manage/2fa', { credentials: 'include' })
.then(r => r.json());
反応
{ "hasAuthenticator": true, "is2faEnabled": true, "isMachineRemembered": false, "recoveryCodesLeft": 8 }
GET /api/auth/manage/authenticator/setup
共有キーとQRコード登録画面用の otpauth:// URIを返します。Verifyエンドポイントとペアリングして2段階認証の有効化を完成させます。
要望
const setup = await fetch('/api/auth/manage/authenticator/setup', { credentials: 'include' })
.then(r => r.json());
反応
{
"sharedKey": "abcd efgh ijkl mnop",
"authenticatorUri": "otpauth://totp/PowerPortalsPro:user@example.com?secret=ABCD…&issuer=PowerPortalsPro"
}
POST /api/auth/manage/authenticator/verify
認証アプリからコードを検証し、2段階認証を有効にします。最初の登録時には、応答は初期の復旧コードのセットも返します。
要望
const res = await fetch('/api/auth/manage/authenticator/verify', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: '123456' })
}).then(r => r.json());
反応
{ "success": true, "recoveryCodes": ["ABC123DEF456", "GHI789JKL012", "…"] }
POST /api/auth/manage/authenticator/reset
認証キーを回転させます。これにより2段階認証も無効化され、ユーザーは再登録しなければなりません。
要望
const { success } = await fetch('/api/auth/manage/authenticator/reset',
{ method: 'POST', credentials: 'include' }).then(r => r.json());
反応
{ "success": true }
POST /api/auth/manage/2fa/disable
アカウントの2段階認証をオフにします。
要望
const { success } = await fetch('/api/auth/manage/2fa/disable',
{ method: 'POST', credentials: 'include' }).then(r => r.json());
反応
{ "success": true }
POST /api/auth/manage/2fa/recovery-codes/generate
既存のセットを置き換えてリカバリコードを再生成し、新しいコードを返します。
要望
const { recoveryCodes } = await fetch('/api/auth/manage/2fa/recovery-codes/generate',
{ method: 'POST', credentials: 'include' }).then(r => r.json());
反応
{ "recoveryCodes": ["ABC123DEF456", "GHI789JKL012", "…"] }
POST /api/auth/manage/2fa/forget-browser
このブラウザの信頼デバイスクッキーがクリアされるため、次のサインイン時に再び2段階認証が必要になります。
要望
const { success } = await fetch('/api/auth/manage/2fa/forget-browser',
{ method: 'POST', credentials: 'include' }).then(r => r.json());
反応
{ "success": true }
GET /api/auth/manage/external-logins
現在アカウントにリンクされている外部ログイン情報が一覧です。
要望
const logins = await fetch('/api/auth/manage/external-logins', { credentials: 'include' })
.then(r => r.json());
反応
{
"currentLogins": [
{ "loginProvider": "Microsoft", "providerKey": "oid-…", "providerDisplayName": "Microsoft" }
]
}
GET /api/auth/manage/login-info
ユーザーのサインインパスの統合ビュー(リンクされた外部ログインとローカルパスワードの設定の有無)を用いて、ログイン解除によってユーザーがロックアウトされるかどうかを判断します。
要望
const info = await fetch('/api/auth/manage/login-info', { credentials: 'include' })
.then(r => r.json());
反応
{
"externalLogins": [ { "loginProvider": "Microsoft", "providerKey": "oid-…", "providerDisplayName": "Microsoft" } ],
"hasLocalPassword": true
}
GET /api/auth/manage/external-logins/link?provider=…
サインイン済みアカウントに追加のプロバイダーをリンクするOAuthフローを開始します。302チャレンジを返すので、取ってくるのではなくそこへ向かうべきです。
要望
// 全ページナビゲーション;以下のコールバックはログイン情報を追加し、リダイレクトします。
window.location.href =
'/api/auth/manage/external-logins/link?provider=Google&returnUrl=' + encodeURIComponent('/account');
反応
// 302 外部プロバイダーの同意ページ→見つかりました。
GET /api/auth/manage/external-logins/link/callback
リンクフローのOAuthコールバック。プロバイダーはブラウザをここにリダイレクトします。サーバーは、最初に提供された returnUrl にログインと302-リダイレクトを添付します。これを直接呼ぶわけじゃない。
要望
// OAuthプロバイダーのリダイレクトで影響を受けますが、あなたのコードが直接呼び出すのではありません。
反応
// 302 リンクエンドポイントに提供されたreturnUrl→見つけ、ログインが添付されています。
POST /api/auth/manage/external-logins/remove
プロバイダー+プロバイダーキーで外部ログイン1つをアンリンクします。
要望
const { success } = await fetch('/api/auth/manage/external-logins/remove', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ loginProvider: 'Microsoft', providerKey: 'oid-…' })
}).then(r => r.json());
反応
{ "success": true }
GET /api/auth/manage/personal-data
ユーザーの個人データ—すべての [PersonalData] 財産とリンクされた外部ログイン—をエクスポートし、GDPRスタイルのダウンロード用にします。
要望
const data = await fetch('/api/auth/manage/personal-data', { credentials: 'include' })
.then(r => r.json());
反応
{
"personalData": { "Id": "550e8400-…", "Email": "john.doe@example.com" },
"externalLogins": { "Microsoft": "oid-…" }
}
POST /api/auth/manage/personal-data/delete
ユーザーのアカウントを永久に削除し、サインアウトします。アカウントに現在のパスワードがある場合は、そのパスワードが必要です。外部専用アカウントにはパス null 。 result 成功、誤ったパスワード、パスワードが必要で提供されていない場合を区別します。
要望
const { result } = await fetch('/api/auth/manage/personal-data/delete', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: 'CurrentP@ss!' }) // 外部のみアカウントにはnull
}).then(r => r.json());
// 結果:0 成功 ·1 誤ったパスワード ·2 RequireLocalPassword
反応
{ "result": 0 }
関連項目
関連文書:
IPowerPortalsProService — これらのエンドポイントを囲むC#ラッパーは、Blazorのコンポーネントが生のHTTPを発行する必要がないときに注入するものです。SystemUserサインイン — なぜ/api/auth/me報告書が一部の校長にはtableName: "systemuser"、他の人にはtableName: "contact"なのかの背景。
