Microsoft Graph Toolkit(mgt)で個人 Microsoft アカウントをサインインさせようとすると「unauthorized_client: The client does not exist or is not enabled for consumers」が出る――本番だけ再現したり、テナント設定やリダイレクト URI を変えた直後に発火する厄介なエラーです。本記事では再発防止まで踏み込んだ決定版の原因整理と対処手順、実装サンプル、検証チェックリストをまとめます。
症状と前提(再現パターンの全体像)
以下のいずれか、あるいは複数が同時に発生します。
- SPA(Single Page Application)で
<mgt-msal2-provider>を用いたサインインを押すと、ダイアログにunauthorized_client: The client does not exist or is not enabled for consumersが表示され、@outlook.com/@hotmail.com 等の個人アカウントがサインイン不可。 - Azure Portal でアプリ登録(App registration)やエンタープライズ アプリケーション(サービス プリンシパル)の割り当てを済ませても改善しない。
- ローカル環境では成功するが、ステージング/本番だけ失敗する。あるいは設定変更(マニフェスト更新・シークレット更新・リダイレクト URI 追加)直後から失敗し始める。
このエラーが意味するもの(原因の読み解き)
メッセージは直訳すると「そのクライアント(アプリ)は存在しない、または consumers(個人 Microsoft アカウント)向けに有効化されていない」です。多くのケースで根本原因は以下に分類されます。
- アプリの対象アカウント種別が不一致:
signInAudienceがAzureADMyOrgやAzureADMultipleOrgsのままで、個人(consumers)を許容していない。 - 認可エンドポイント(authority)が不適切:テナント固有の
https://login.microsoftonline.com/<tenantId>を指定しており、common/consumers を使っていない。 - リダイレクト URI の種類や値が不整合:SPA なのに「Web」として登録、もしくは www 有無/末尾スラッシュ/パスの違い/HTTP⇔HTTPS の差異。
- v1 と v2 の混在:v2 トークン(スコープベース)で動作すべきところに v1(リソースベース)を混ぜている、
/.defaultの誤用など。 - ライブラリの取り違え:
mgt-msal-provider(MSAL v1)とmgt-msal2-provider(MSAL v2)が混在。 - 環境差・キャッシュ・CORS:本番だけドメインやポートが違い、登録されていない/ブラウザのキャッシュや ITP の影響。
最短で直すための「先に見るべき」チェックリスト
まずは次の 6 点を上から順に確認してください。多くはこれで即解決します。
- アプリ マニフェストの
accessTokenAcceptedVersion=2とsignInAudience="AzureADandPersonalMicrosoftAccount"を設定。 - リダイレクト URI を 「Single‑page application」 として 環境ごとに正確に登録。
- 認可先
authorityをhttps://login.microsoftonline.com/common(または/consumers)に変更。 - v2 前提の構成(
scopes使用・v2.0 エンドポイント)に統一。v1 パラメータ(resource等)を排除。 - Graph Toolkit のバージョンを固定し、
<mgt-msal2-provider>を使用しているか再確認。 - 本番ドメインの CORS/リダイレクト URI/末尾スラッシュ とブラウザキャッシュを同時に確認。
対処の全体サマリー(一覧表)
| 対応内容 | 詳細 | 備考 |
|---|---|---|
| 1. マニフェスト設定の見直し | accessTokenAcceptedVersion を 2、signInAudience を “AzureADandPersonalMicrosoftAccount” に。 | 個人アカウントを許可しない既定(AzureADMyOrg)のままだと本エラー。 |
| 2. リダイレクト URI の正確な登録 | SPA の場合は「Single‑page application」として登録。例:http://localhost:3000/、https://example.com/ 等。 | ローカル・ステージング・本番で全て登録必須。末尾スラッシュや www 有無に注意。 |
| 3. シークレット/証明書の更新 | SPA 単体では通常クライアント シークレットは不要。バックエンド(Web API)を伴う構成なら、更新後の値を確実に反映。 | 失効時は 401/403 など別エラーに見えることも。構成を整理。 |
| 4. テナント共通エンドポイントの利用 | authority を .../common または .../consumers に設定。 | 特定 tenantId を指定すると個人アカウントが弾かれる。 |
| 5. “ユーザーの割り当てが必要” の見直し | エンタープライズ アプリケーションで User assignment required を No に。必要時のみ個別割り当て。 | 主に組織アカウント向けのガード。混同に注意。 |
| 6. v2.0 エンドポイントに統一 | /.default や v1 エンドポイントの混在を排除。スコープは User.Read 等の v2 形式。 | v1 トークンは個人アカウントと非互換。 |
手順詳細:マニフェストの正解例と落とし穴
アプリ登録 > マニフェストで次の 2 つを必ず確認します。
{
"accessTokenAcceptedVersion": 2,
"signInAudience": "AzureADandPersonalMicrosoftAccount"
}
- accessTokenAcceptedVersion:
2にすることで、v2(スコープベース)のトークンを受け入れます。 - signInAudience:
AzureADandPersonalMicrosoftAccountを選ぶと、「任意の組織ディレクトリ + 個人 Microsoft アカウント」が対象になります。
ここが AzureADMyOrg(自テナント限定)や AzureADMultipleOrgs(個人を含まない)だと、consumers が拒否され上記エラーになります。
手順詳細:リダイレクト URI は「Single‑page application」で登録
MSAL v2(PKCE)で動作する SPA は、プラットフォームの種類として「Single‑page application」を選ぶ必要があります。「Web」で登録すると コード+シークレット前提になり、リダイレクトの検証が合わず認可が失敗しがちです。
| 環境 | 推奨リダイレクト URI の例 | 注意点 |
|---|---|---|
| ローカル | http://localhost:3000/ もしくは http://localhost:5173/ 等、実際の開発ポート | WordPress 等で /index.html を付けるなら、登録値と完全一致させる |
| ステージング | https://stg.example.com/ | www の有無・サブドメインを間違えやすい |
| 本番 | https://example.com/ または https://www.example.com/ | 末尾スラッシュの有無差で失敗する例が多い |
MSAL(および mgt)は 完全一致で照合します。トレーリングスラッシュ・大文字小文字・パスは別 URL とみなされます。
手順詳細:authority は common/consumers を使う
個人アカウントを受け入れるなら、テナントを固定しない authority を使います。
https://login.microsoftonline.com/common
または個人のみを対象にする場合は次を使います。
https://login.microsoftonline.com/consumers
.../<tenantId> や .../organizations を使うと、consumers が拒否され unauthorized_client が発生します。
v2 前提に統一(パラメータ・エンドポイント・スコープ)
- v2 では scope(例:
User.Read)を要求します。v1 のresourceパラメータを混ぜない。 - 認可/トークン エンドポイントは
/oauth2/v2.0/authorizeと/oauth2/v2.0/token。 /.defaultは機密クライアント(サーバー側)の用途が中心。SPA では明示スコープ(User.Read等)を指定するのが安全。
Graph Toolkit(mgt)の正しい使い方
Web Components(マークアップ)での最小実装
<!-- mgt ローダー(例) -->
<script type="module" src="https://unpkg.com/@microsoft/mgt@3/dist/bundle/mgt-loader.js"></script>
login-typeは Popup 推奨(Redirect でも可)。Redirect を使う場合は その URL を SPA のリダイレクト URI に登録します。scopesは v2 形式で列挙します(カンマ区切りでもスペース区切りでも可)。
プログラムから初期化する場合(TypeScript/React 等)
import { Providers } from '@microsoft/mgt-element';
import { Msal2Provider } from '@microsoft/mgt-msal2-provider';
Providers.globalProvider = new Msal2Provider({
clientId: '',
authority: '[https://login.microsoftonline.com/common](https://login.microsoftonline.com/common)',
loginType: 'Popup',
scopes: ['User.Read', 'Mail.Read'],
// redirectUri: '[https://example.com/auth/callback](https://example.com/auth/callback)', // Redirect を使うときは登録必須
});
バージョン整合性(mgt と MSAL)
- mgt v2.7 以前から移行したプロジェクトでは、古い
mgt-msal-providerを読み込んでいる可能性があります。必ずmgt-msal2-providerに統一し、package.json でバージョンを固定してください。 - 依存の二重取り込み(CDN と npm の併用)で Umd/ESM の衝突が起きると、設定が反映されないことがあります。
ローカルは通るのに本番で落ちる ― よくある差分
| 症状 | 原因の典型 | 対処 |
|---|---|---|
本番のみ unauthorized_client | 本番ドメインのリダイレクト URI 未登録/www 有無・スラッシュ違い | SPA(Single‑page application)として本番 URL を完全一致で登録 |
| ステージングのみ失敗 | authority を /<tenantId> 固定にしている | /common(または /consumers)へ変更 |
| 設定変更直後だけ失敗 | ブラウザの localStorage/sessionStorage に古いキャッシュ | キャッシュ削除・シークレットタブ・sessionStorage を使う構成で暫定回避 |
| Safari/一部ブラウザのみ失敗 | ITP/サードパーティ Cookie 制限の影響 | Popup ログインへ切替、またはトラッキング防止例外の案内 |
| 突然エラー化 | リダイレクト URI を「Web」で追加してしまい構成が不整合 | 誤登録を削除し、「Single‑page application」で再登録 |
デバッグの観点(Network/Console/ログ)
Network タブで見るべきパラメータ
/oauth2/v2.0/authorize リクエストのクエリを確認します。
- authority のホスト・パス:
login.microsoftonline.com/commonになっているか。 - scope:
User.Readなど v2 形式か。resourceは混ざっていないか。 - redirect_uri:登録済みの URI と完全一致か(末尾
/を含むか)。 - client_id:誤った App を参照していないか。
MSAL ログの出力(詳細診断)
import { LogLevel } from '@azure/msal-browser';
const msalConfig = {
auth: {
clientId: '',
authority: '[https://login.microsoftonline.com/common](https://login.microsoftonline.com/common)',
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) return;
switch (level) {
case LogLevel.Error: console.error(message); break;
case LogLevel.Warning: console.warn(message); break;
case LogLevel.Info: console.info(message); break;
default: console.debug(message); break;
}
}
}
},
cache: {
cacheLocation: 'sessionStorage'
}
};
ここで containsPii(個人情報を含むログ)は捨て、Error/Warning を重点的に確認します。
「ユーザーの割り当てが必要」設定の扱い
エンタープライズ アプリケーション側で User assignment required を No にすると、組織アカウントに対してアクセスが柔軟になります。個人アカウント(consumers)には本来適用外ですが、同一アプリで組織ユーザーも扱う場合の副作用として、割り当て必須だとログイン後のトークン取得や同意フローで詰まることがあります。
シークレット/証明書の注意点(SPA と Web API の境界)
- 純粋な SPA(フロントのみ)ではクライアント シークレットは使用しません。コードを公開できないためです。
- 独自のバックエンド API を持ち OBO(On-Behalf-Of)等を行う場合は、サーバー側にシークレットまたは証明書が必要です。更新したら API 側に必ず反映します。
- 「シークレット更新後に SPA が突然失敗」は、実は App を作り直した/Client ID を入れ替えた/リダイレクト URI を消したなどの構成変更が原因であることが多いです。
CI/CD での再発防止(IaC と自動検証)
環境差が原因のトラブルは、Infrastructure as Code で 0/1 の差異として管理すると劇的に減ります。例として Bicep/Terraform の断片を示します。
Bicep(抜粋)
resource app 'Microsoft.Graph/[email protected]' = {
name: 'my-mgt-app'
properties: {
signInAudience: 'AzureADandPersonalMicrosoftAccount'
web: { implicitGrantSettings: { enableIdTokenIssuance: true } }
spa: {
redirectUris: [
'http://localhost:3000/'
'https://stg.example.com/'
'https://example.com/'
]
}
api: { requestedAccessTokenVersion: 2 }
}
}
Terraform(抜粋)
resource "azuread_application" "mgt" {
display_name = "my-mgt-app"
sign_in_audience = "AzureADandPersonalMicrosoftAccount"
web {
implicit_grant {
id_token_issuance_enabled = true
}
}
spa {
redirect_uris = [
"http://localhost:3000/",
"[https://stg.example.com/](https://stg.example.com/)",
"[https://example.com/](https://example.com/)"
]
}
api {
requested_access_token_version = 2
}
}
CI で manifest.json をエクスポートし、次の項目を自動テストするのも有効です。
signInAudience === AzureADandPersonalMicrosoftAccountaccessTokenAcceptedVersion === 2(または API セクションの v2)- 各環境のリダイレクト URI がすべて含まれている
実装誤りを発見するクイックテスト
- 本番 URL をそのままブラウザで開き、開発者ツールの Network を開いた状態で「サインイン」をクリック。
/authorizeリクエストのauthorityが/commonかを確認。redirect_uriが登録済みの値(末尾スラッシュ含む)と一致しているか比較。scopeにUser.Read等の v2 スコープが入っているか。- ここで相違があればその場で修正し、ブラウザの localStorage/sessionStorage をクリアして再試験。
エラーメッセージ別の対策マップ
| メッセージ | 原因の目星 | 最初にすること |
|---|---|---|
unauthorized_client ... not enabled for consumers | 個人アカウントが許可されていない/authority が tenant 固定 | signInAudience を変更、/common に切替 |
invalid_client | Client ID の誤り、アプリ削除・再作成の影響 | Client ID を見直し、キャッシュ削除 |
invalid_request: The reply url is invalid | リダイレクト URI 不一致(末尾 /、www など) | SPA として正確に再登録 |
interaction_required | 同意不足・Conditional Access・割り当て要件 | 権限の管理者同意、割り当て設定を見直し |
Next.js / Vite / 静的ホスティングでの注意
- ベースパス(
/app/など)を使うときは、リダイレクト URI も同じパスで登録。 - SPA で ルーティングにハッシュ(
#)を使う場合、MSAL の redirectUri と実際の URL 表現が一致しているかを確認。 - CDN キャッシュで古い JS を配信していると、authority や scopes の修正が反映されません。キャッシュパージを忘れずに。
セキュリティ観点のミニチェック
- SPA にクライアント シークレットを埋め込まない(漏えい前提)。
- 必要最小限のスコープだけを要求(例:まずは
User.Read)。 - https を強制(ローカル以外)。
よくある質問(FAQ)
Q. authority は /common と /consumers のどちらが良い?
組織アカウントも個人も受け入れるなら /common。個人のみに限定したい要件があるなら /consumers を選びます。
Q. /.default を SPA で使っても良い?
推奨は明示スコープ(例:User.Read)。/.default は機密クライアントの同意モデルで使われることが多く、誤用すると権限不足や同意の不整合を招きます。
Q. エンタープライズ アプリケーションの「ユーザー割り当てが必要」は個人アカウントに影響する?
原則は組織アカウント向けの制御です。ただし混在シナリオでは、同意やトークン発行の前段で影響を受けることがあるため、不要であれば No を推奨します。
完全チェックリスト(貼って使える)
- マニフェスト:
accessTokenAcceptedVersion=2/signInAudience="AzureADandPersonalMicrosoftAccount" - authority:
https://login.microsoftonline.com/common(または/consumers) - リダイレクト URI(SPA):ローカル/ステージング/本番を 完全一致で登録
- スコープ:
User.Readなど v2 形式に統一(resourceを使わない) - mgt:
<mgt-msal2-provider>を使用、依存のバージョン固定 - ブラウザ:localStorage/sessionStorage をクリアして再試験
- CDN:キャッシュパージ済み
- (必要に応じて)エンタープライズ アプリの割り当て設定を確認
まとめ(再発させない運用の勘所)
unauthorized_client: The client does not exist or is not enabled for consumers は、要するに「アプリが個人アカウントを受け入れる構成になっていない」か「その前提と矛盾する URL・エンドポイントを叩いている」ときに起きます。最重要は マニフェスト(signInAudience) と リダイレクト URI(SPA として完全一致)、そして authority を /common にすること。ここを押さえたうえで、v2 への統一・依存バージョン固定・IaC による自動検証を行えば、ローカルと本番の差分やメンテ作業後の謎の失敗はほぼ撲滅できます。
実践テンプレート:最小構成の HTML(そのまま検証可)
以下は最小限の構成で、個人アカウント+組織アカウントの両方に対応します。client-id と authority を調整し、事前に同じ URL を SPA のリダイレクト URI へ登録してください。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>mgt-msal2-provider quickstart</title>
<script type="module" src="https://unpkg.com/@microsoft/mgt@3/dist/bundle/mgt-loader.js"></script>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; margin: 2rem; }
.row { display: flex; gap: 1rem; align-items: center; }
</style>
</head>
<body>
<mgt-msal2-provider
client-id="<YOUR-CLIENT-ID>"
authority="https://login.microsoftonline.com/common"
login-type="Popup"
scopes="User.Read"
></mgt-msal2-provider>
<div class="row">
<mgt-login></mgt-login>
<mgt-person person-query="me" view="twoLines"></mgt-person>
</div>
<!-- 認証後に API を叩く例 -->
<script type="module">
import { Providers } from "https://unpkg.com/@microsoft/mgt@3/dist/es6/index.js";
async function whoAmI() {
const provider = Providers.globalProvider;
if (provider && provider.state === 2) { // SignedIn
const graph = provider.graph.client;
const me = await graph.api('/me').get();
console.log(me);
}
}
document.addEventListener('mgt:stateChange', whoAmI);
</script>
現場メモ:落ちやすい 12 のポイント
- マニフェストの 保存忘れ(Portal で変更後は Save を必ず押す)。
- Client ID の取り違え(本番と開発の App を別に作っている)。
- Redirect URI の 末尾スラッシュ違い。
- www 有無の違い(
https://example.com/とhttps://www.example.com/は別物)。 - authority を
/organizationsにしてしまう。 - スコープと
/.defaultの混在。 - v1 の
resourceパラメータがどこかに残っている。 - mgt の旧プロバイダー(
mgt-msal-provider)を読み込んでいる。 - CDN のキャッシュが古い。
- Popup をブロックするブラウザ設定。
- Safari の ITP でサインイン後に状態が飛ぶ。
- リダイレクト URI を「Web」で登録し直してしまった。
最終チェック:これで “unauthorized_client” は消える
最後に、今回の要点を 3 行で。
- マニフェスト:
signInAudience="AzureADandPersonalMicrosoftAccount"とaccessTokenAcceptedVersion=2 - エンドポイント:
authority=/common(または/consumers)+ v2(/oauth2/v2.0) - リダイレクト URI: 「Single‑page application」で 環境ごとに完全一致で登録
この 3 点を順に見直すだけで、ほとんどのケースは即座に復旧します。加えて、依存のバージョン固定と IaC による自動検証を取り入れれば、ステージング/本番の差分起因の再発を長期的に防止できます。

コメント