ASP.NET Web API を .NET 8 ベースへ更新し、認証を ADAL(ROPC)から MSAL に切り替えた直後に AADSTS50146(invalid_request : This application is required to be configured with an application‑specific signing key …)でトークン取得が失敗する――この現象は、旧 v1(ADAL)時代のレガシー設定が残存していることが主因です。本稿は「壊さずに移行する」観点で、根本原因の理解から安全な対処手順、検証コード例までを一気通貫で解説します。
結論と全体像(最速の意思決定)
エラー AADSTS50146 は、アプリ登録に残った「アプリ固有の署名鍵(application‑specific signing key)を要求する」レガシー設定が、v2 エンドポイント(MSAL)では無効であるにも関わらず有効化されているときに発生します。根本対処は次の二択です。
| 対応策 | 内容 | 備考・補足 |
|---|---|---|
| A. 新しい v2 アプリ登録を作成(推奨) | 旧 ADAL 用アプリ登録は残し、MSAL(v2 エンドポイント)専用に 別アプリ登録を新規作成。クライアントと API のバインドを段階的に切り替える。 | 安全性が最も高い。既存運用への副作用がない/ロールバック容易。 |
| B. 既存アプリ登録を修正して流用 | ポータルの App registrations → 対象アプリ → Manifest で、 ① レガシー「アプリ固有の署名鍵」を 無効化(該当項目を削除または false 化) ② accessTokenAcceptedVersion を 2 に設定③ 保存後、数分待って再試行 | v1 時代の非推奨フラグを除去する作業。慎重さが必要(影響範囲の把握とバックアップ)。 |
要点: v2(MSAL)は application‑specific signing key を前提にしません。残存していると AADSTS50146 でブロックされます。安全第一なら「A」(新規 v2 アプリ登録)を選び、速攻で解消したい場合のみ「B」(既存の修正)を選びます。
AADSTS50146 が起きる仕組みを 3 分で理解する
ADAL が主に利用した v1 エンドポイントでは、一部のアプリ種別で「テナント共通の署名鍵ではなく、アプリ専用の署名鍵でトークンを検証する」動線を選べるレガシー設定が存在しました。MSAL(v2 エンドポイント)は設計が異なり、このレガシー要求は無視できません。要求が残ったまま v2 にアクセスすると、「その専用鍵を設定していないので処理できない」という趣旨で AADSTS50146 が返されます。
- ADAL(v1):
resourceパラメータ。レガシー SSO 設定やアプリ専用署名鍵の影響を受ける構成が存在。 - MSAL(v2):
scopes(推奨.default)。共通の署名鍵と OpenID Connect/OAuth 2.0 の標準に忠実。
→ レガシー設定が残ると 整合性が崩れて AADSTS50146。
| 観点 | ADAL(v1) | MSAL(v2) |
|---|---|---|
| トークンエンドポイント | /oauth2/token | /oauth2/v2.0/token |
| リクエスト パラメータ | resource | scope(例:api://{API-ID-URI}/.default) |
| トークン バージョン | v1(デフォルト) | v2(API 側の accessTokenAcceptedVersion=2 が鍵) |
| アプリ固有の署名鍵 | 一部シナリオで要求可 | 前提なし(残存要求があると AADSTS50146) |
推奨:新しい v2 アプリ登録を作って切り替える(対応策 A)
運用を壊さずに MSAL 化する鉄板のやり方です。旧 ADAL アプリは温存し、並行で v2 専用のアプリ登録を作って段階的にトラフィックを切り替えます。
クライアント(ROPC 呼び出し側)の設定
- アプリ登録を新規作成(シングルテナント推奨:Accounts in this organizational directory only)。
- Authentication → Advanced settings → Allow public client flows を Yes(ROPC に必須)。
- API permissions:アクセス対象の API を追加。Graph なら
https://graph.microsoft.com/.defaultを利用する想定で付与・同意。
API(受け側 Web API/.NET 8)の設定
- Expose an API:Application ID URI を設定(例:
api://{API-APP-ID-GUID})。 - Manifest:
accessTokenAcceptedVersionを 2 に。これはAPI 側で設定する点に注意(API が v2 トークンを受け付けることを宣言)。
ROPC の最小サンプル(MSAL/.NET 8, C#)
// 実行環境: .NET 8, Microsoft.Identity.Client (MSAL) パッケージ
using Microsoft.Identity.Client;
using System.Net;
using System.Security;
var tenantId = "{TenantId}";
var clientId = "{PublicClient_AppId}"; // クライアント(ROPC 呼び出し側)のアプリID
var username = "{[[email protected]](mailto:[email protected])}";
var password = "{PlainTextPassword}"; // 実運用では安全な保管と入力経路を用意すること
var apiIdUri = "api://{ApiAppIdOrUri}";
var scopes = new[] { $"{apiIdUri}/.default" }; // Graph の場合は "[https://graph.microsoft.com/.default](https://graph.microsoft.com/.default)"
var app = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority($"[https://login.microsoftonline.com/{tenantId}](https://login.microsoftonline.com/{tenantId})")
.Build();
using var secure = new NetworkCredential("", password).SecurePassword;
var result = await app.AcquireTokenByUsernamePassword(scopes, username, secure).ExecuteAsync();
Console.WriteLine($"AccessToken (truncated): {result.AccessToken[..50]}...");
Console.WriteLine($"Token for audience: {result.Account?.Username} / ExpiresOn: {result.ExpiresOn}");
注意: ROPC は UI なし認証のため、MFA やデバイス準拠が 必須のユーザーでは失敗します。運用上は「MFA/条件付きアクセスの対象外」にしたサービス アカウントを用意する、または ROPC 自体の利用を最小化してください。
.NET 8 Web API(受け側)の最小構成
API は v2 トークンを受け付ける設定で JWT を検証します。
// Program.cs(.NET 8 Minimal API の一例)
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
var tenantId = "{TenantId}";
var apiAppId = "{Api_AppId}"; // 受け側 API のアプリ登録ID
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = $"[https://login.microsoftonline.com/{tenantId}/v2.0](https://login.microsoftonline.com/{tenantId}/v2.0)";
options.Audience = apiAppId; // または Application ID URI を受ける場合は適宜設定
// 追加の検証パラメータは要件に応じて調整
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/whoami", (HttpContext ctx) =>
{
var name = ctx.User.Identity?.Name ?? "(anonymous)";
return Results.Ok(new { name });
}).RequireAuthorization();
app.Run();
既存アプリ登録を修正して流用する(対応策 B)
短時間で切り替えたい場合の現実解です。作業前に Manifest のバックアップを必ず取得してください。
- Azure ポータル → App registrations → 対象アプリ → Manifest を開く。
- レガシーの application‑specific signing key に相当する項目を 削除 or false に変更(テナントによって表記が異なるため、SigningKey, SigningCertificate, UseCustomSigningKey 等のラベルを目印に「要求を外す」)。
accessTokenAcceptedVersionを 2 に設定(特に受け側 API のアプリ登録)。- 保存し、数分のレプリケーション待ちの後に ROPC を再試行。
Manifest のイメージ(記述名は環境差があるため 参考):
{
"signInAudience": "AzureADMyOrg",
"accessTokenAcceptedVersion": 2,
"publicClient": { "redirectUris": [] },
"isFallbackPublicClient": true,
// ↓ もしアプリ固有の署名鍵を要求する設定/痕跡があれば削除
// "useCustomSigningKey": false,
// "preferredTokenSigningKeyThumbprint": null,
// "preferredTokenSigningKeyEndDateTime": null
}
補足:
accessTokenAcceptedVersionは「API(リソース)側」の宣言です。クライアント側に設定しても、API が v2 トークンを受け付けない限り効果は出ません。
また、Allow public client flows は ROPC を行う「クライアント」の設定です。両者を取り違えないことが重要です。
ROPC 設定チェックリスト(最終確認)
- Allow public client flows:クライアント側で On。
- Authority:
https://login.microsoftonline.com/{TenantId}(テナント固有)。/commonは避ける。 - Scope:
{ResourceAppIdUri}/.default(Graph はhttps://graph.microsoft.com/.default)。ROPC では動的スコープ要求を避ける。 - API 側 Manifest:
accessTokenAcceptedVersion = 2。 - MFA / 条件付きアクセス:対象ユーザーが 要求されないポリシー範囲にあること。
- パスワード有効 / ロックなし:AADSTS 50126/50053 などの基礎エラーも併発しやすい。
よくあるエラーと対処
| エラー | 意味 | 対処 |
|---|---|---|
| AADSTS50146 | アプリ固有の署名鍵を要求するレガシー設定が残存 | 新規 v2 アプリ登録へ切替(推奨)または Manifest から要求を削除+API 側を v2 受け入れに |
| AADSTS50076/50079 | MFA が必要 | ROPC 対象ユーザーを MFA 対象外へ/別フロー(デバイスコード/インタラクティブ)へ移行 |
| AADSTS53003 | 条件付きアクセスでブロック | 準拠デバイス/ロケーション等の要件を満たすか、ポリシーで例外を付与 |
| AADSTS50126 | 資格情報が無効 | ユーザー名/パスワードの再確認・ロック解除・再発行 |
| AADSTS700016/7000218 | アプリ ID 不正/シークレット不正 | クライアント ID/シークレットの一致確認(ROPC パブリック クライアントでは通常シークレット不要) |
cURL での再現・切り分け(手動検証)
コードやライブラリに依存せず、エンドポイントと構成の問題を切り分けるときに有効です。
# v2 エンドポイント(/oauth2/v2.0/token)で ROPC を実行
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id={PublicClient_AppId}" \
-d "grant_type=password" \
-d "username={[email protected]}" \
-d "password={PlainTextPassword}" \
-d "scope=api://{ApiAppIdUri}/.default" \
"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/token"
HTTP 200 でアクセストークンが返れば構成はおおむね正しいです。AADSTS50146 が返る場合は、前述のレガシー要求が残っています。
移行の落とし穴(現場あるある)
- API 側の v2 化を忘れる: クライアントを MSAL 化しても、API が
accessTokenAcceptedVersion=2でないと整合しません。 - ユーザーに MFA がかかっている: ROPC は UI なしのため、MFA/CA で失敗します。サービス アカウントの分離を。
- Scope と Resource の混同: ADAL 時代の
resourceをそのまま持ち込むと失敗します。.defaultを使って権限セット(アプリに付与済み権限)を要求します。 - ポータルの反映待ちを見落とす: Manifest 保存直後はレプリケーション待ちが必要です(数分)。すぐの再試行で「直っていない」と誤判定しがち。
安全な段階的切り替えプラン(推奨運用)
- 新規 v2 アプリ登録を用意(クライアント/API 双方)。
- ステージング環境で ROPC → API の往復を cURL + MSAL サンプルで検証。
- ユーザーまたはジョブ単位で段階リリース(失敗時は即ロールバック)。
- 旧 ADAL 依存が残るワークロードを最小化し、最終的に撤去。
セキュリティの実務ポイント
- ROPC 自体は非推奨フロー(パスワード長期保管リスク)。可能なら Device Code や Auth Code + PKCE へ移行。
- やむを得ず ROPC を使う場合は、専用アカウント・最小権限・監査ログ監視・資格情報の期限管理を徹底。
- 条件付きアクセスの例外は 最小スコープで。「ユーザー単位+アプリ限定+ネットワーク制限」で被害半径を抑制。
トラブルシュート高速化のコツ
- エラー本文は必ず保存: error, error_description, correlation_id, timestamp を記録。
- 最小再現(cURL)とコード経路(MSAL)を両方試す。ライブラリのラッパーや設定ミスを分離。
- アプリ登録オブジェクト(Application)とエンタープライズ アプリ(Service Principal)の違いを意識。修正対象を取り違えない。
現場チェックリスト(保存版)
| 項目 | 見る場所 | 判定基準 |
|---|---|---|
| Allow public client flows | クライアント → Authentication | Yes(ROPC 有効) |
| Authority | クライアント コード | https://login.microsoftonline.com/{TenantId}(テナント固有) |
| Scope | クライアント コード | {ResourceAppIdUri}/.default または https://graph.microsoft.com/.default |
| トークン受け入れバージョン | API → Manifest | accessTokenAcceptedVersion = 2 |
| アプリ固有の署名鍵の要求 | 対象アプリ → Manifest | 該当項目が存在しない/無効 |
| MFA/CA の影響 | ユーザーのポリシー | ROPC 対象ユーザーは除外 or 要件を満たす |
まとめ
- AADSTS50146 は application‑specific signing key を要求するレガシー設定が原因。
- 最も安全なのは「A:新しい v2 アプリ登録」で切り替え。既存を活かすなら「B:Manifest 修正」。
- ROPC を成立させる鍵は Allow public client flows / tenant 固有 Authority / .default スコープ / API 側の v2 受け入れ。
- 運用では ROPC を最小化し、将来的にはインタラクティブ系フローへの移行を視野に。
付録:PowerShell で Manifest を素早く確認・更新
自動化が必要な現場向けの参考スニペットです(実行前に権限と影響範囲を必ず確認してください)。
# Microsoft Graph PowerShell を想定
# 接続
Connect-MgGraph -Scopes "Application.ReadWrite.All"
# 対象アプリを取得
$app = Get-MgApplication -Filter "appId eq '{Api_AppId}'"
# accessTokenAcceptedVersion を 2 に
Update-MgApplication -ApplicationId $app.Id -AccessTokenAcceptedVersion 2
# (必要に応じて)レガシー署名鍵関連のプロパティを無効化/削除
# 実環境のプロパティ名に応じて編集
付録:移行時の変更点リマインド
- resource → scope:MSAL では
scope=.defaultが基本。アプリに付与済みの権限束を要求します。 - v2 トークン:API 側が v2 を受け付けるよう
accessTokenAcceptedVersion=2を忘れずに。 - レガシー設定の撤去:application‑specific signing key 要求は撤去。残ると AADSTS50146。
最後に
本稿の手順に沿えば、旧 ADAL 環境を壊さずに MSAL(ROPC)へ移行し、AADSTS50146 を確実に解消できます。まずは新規 v2 アプリ登録で検証し、段階的に切り替える――それが最短・最安全のルートです。

コメント