Xamarin.AndroidでMSALを使ってAzure AD(シングルテナント)にサインインすると、突然「Are you trying to sign in to {アプリ名}?」の確認ダイアログが出て、Continue/Cancelを押しても戻らない…そんな現象に遭遇したときの切り分けと、実際に解決した設定(Embedded WebView化)をまとめます。
症状:確認ダイアログは出るのにアプリへ戻らない
現象として多いのは、サインイン開始(AcquireTokenInteractive)後に、Microsoftの確認ダイアログが表示され、Continue / Cancel を押しても画面が閉じず、アプリ側の処理も進まないパターンです。ところが、Azureポータル(Microsoft Entra 管理センター)のサインインログを見ると、Continue を押すたびに「成功」が増えていきます。
この状態は、利用者目線では「固まった」「ボタンが反応しない」に見えますが、実際には認証自体は進んでいるのに、アプリへ戻るためのコールバック(リダイレクト)だけが完了していないケースが多いです。つまり、問題の焦点は「認証の可否」ではなく、戻り(アクティビティ復帰/ディープリンク受け取り)にあります。
| 観測できること | 示唆される状況 |
|---|---|
| Continue/Cancel を押しても画面が閉じない | 外部ユーザーエージェント(システムWebView/ブラウザ/ブローカー)からアプリへの復帰が成立していない |
| サインインログでは成功が記録される | Azure AD 側の認証・トークン発行(またはその直前)までは到達している |
| 同じ端末・同じアカウントで再現する/しないがある | Androidのブラウザ実装、System WebView の更新状況、ブローカー(Microsoft Authenticator等)の有無、端末OEM差が影響する可能性 |
切り分けの最短ルート:サインインログで「割り込み」を見る
まずやるべきは、アプリの例外ログだけで悩まず、サインインログ側のエラーコードを確認することです。今回のパターンでは、ログに次のようなコードが混ざっていることがあります。
- AADSTS50199(
CmsiInterrupt/interaction_required) - 結果が「成功」に見えても、詳細に「追加のユーザー操作が必要」といった痕跡が残る
AADSTS50199(CmsiInterrupt)はざっくり言うと「この要求は、ユーザーの確認・追加操作を挟まないと先へ進めない」という割り込みです。アプリから見れば待ち状態になり、ユーザーからは「Continueを押しても何も起きない」ように見えます。
ポイント
このとき、Continue を押すたびにサインインログが成功して増えるのは「割り込みの確認→認証成功までは行けている」ことを示します。一方で、アプリに戻る経路(リダイレクト受信)が成立しないと、MSAL側のタスクは完了せず、UIも更新されません。
なぜ起きる?「システム WebView(外部ブラウザ相当)」と戻り処理の相性
MSALの対話型認証は、Androidでは一般的にシステムブラウザ(Chrome Custom Tabs)やブローカー(Microsoft Authenticator等)を経由して実行されます。ここで「Are you trying to sign in to {アプリ名}?」のような確認が入ると、フローがアプリ → 外部 → 外部(確認) → アプリと多段になります。
この多段遷移のどこかで、次のような条件が重なると「確認ダイアログ側では進んだのに、アプリ側には戻れない」状態が発生しやすくなります。
- 親 Activity / Window が正しく渡されていない(MSALが復帰先UIを特定できない)
- リダイレクトURIの受け口(Intent-filter)が不足/不一致(戻りのディープリンクをAndroidが正しく配送できない)
- Android 12+ の exported 要件や launchMode の影響で、コールバック用Activityが起動できない/別タスクになる
- 端末のデフォルトブラウザ、System WebView のバージョン差により挙動が変わる
もちろん根本原因は環境により異なりますが、今回のようにサインインログは成功しているのにアプリが待ち続ける場合、「外部に出たまま戻って来られない」問題として扱うと解決が早いです。
実際に解決した対策:Embedded WebView で AcquireTokenInteractive を実行する
今回、最も確実に効いたのが、対話型認証を埋め込み WebView(Embedded WebView)に切り替える方法です。外部ブラウザ相当のユーザーエージェントを使わず、アプリ内で完結させることで、確認ダイアログの表示自体が抑制され、戻り処理の不一致も回避できます。
実装はシンプルで、AcquireTokenInteractive のチェーンに .WithUseEmbeddedWebView(true) を追加します。Xamarin.Androidの場合、あわせて .WithParentActivityOrWindow(...) を必ず指定してください。
result = await authContext.AcquireTokenInteractive(Scopes)
.WithAuthority(Authority)
.WithUseEmbeddedWebView(true) // ★ 追加:Embedded WebView で実行
.WithParentActivityOrWindow(pwindow) // ★ 重要:親Activity/Windowを渡す
.ExecuteAsync();
この設定により、問題の確認ダイアログが出なくなり、Continue/Cancel が効かないように見える状態も解消します。
どこに書く?
.WithUseEmbeddedWebView(true) は、ログイン処理で AcquireTokenInteractive を呼んでいる箇所(サインインボタン押下、起動時の再認証、トークン失効時の再取得など)に、メソッドチェーンとして追加します。
「アプリが戻って来ない」系の問題は、実は WithParentActivityOrWindow の未指定で起きていることも少なくありません。Embedded WebView を入れる場合も、入れない場合も、まずここは確実に押さえてください。
実装例:サイレント取得→必要ならインタラクティブ(Embedded)
実運用では、毎回対話型にするとUXが悪いので、まずはサイレント(AcquireTokenSilent)で取りに行き、MsalUiRequiredException のときだけ対話型にフォールバックする形が一般的です。
var scopes = new[] { "User.Read" }; // 例:必要なスコープに置き換え
AuthenticationResult result = null;
// 1) まずはサイレント
var accounts = await pca.GetAccountsAsync();
var account = accounts.FirstOrDefault();
try
{
result = await pca.AcquireTokenSilent(scopes, account)
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// 2) UIが必要なら対話型(Embedded)
result = await pca.AcquireTokenInteractive(scopes)
.WithAuthority(authority) // シングルテナントなら tenant を固定してもOK
.WithUseEmbeddedWebView(true)
.WithParentActivityOrWindow(activity)
.ExecuteAsync();
}
// result.AccessToken をAPI呼び出しに利用
Embedded WebView を選ぶ前に知っておきたいこと(メリット・デメリット)
Embedded WebView は「戻り問題」を回避できる強力な選択肢ですが、万能ではありません。特にSSOやポリシー適用、端末のブローカー連携の面で、システムブラウザ方式と差が出ます。導入前にチーム内で合意しておくと、あとからの差し戻しを防げます。
| 観点 | システムブラウザ(Custom Tabs等) | Embedded WebView |
|---|---|---|
| SSO(他アプリ/ブラウザと共有) | 強い(ブラウザ/ブローカーのセッションを使える) | 弱い(アプリ内WebViewのため共有されにくい) |
| 端末差・既定ブラウザ差の影響 | 受けやすい(端末/ブラウザ更新で変わる) | 比較的受けにくい(アプリ内で完結) |
| 戻り(リダイレクト)失敗のリスク | 発生しうる(Intent/Activity/ブラウザ連携に依存) | 低い(同一アプリ内で完結しやすい) |
| セキュリティ/推奨度 | 一般に推奨されやすい | 制約が出る場合がある(ポリシー/監査要件に注意) |
「現場で今すぐ止血したい」「特定端末だけ詰まって問い合わせが来る」場合は、Embedded WebView は現実的な解決策になります。一方で、長期的にSSOやポリシー要件が重要なら、後述のチェックリストでシステムブラウザ方式を安定させる道も検討してください。
Embedded WebView でも詰まりやすい実装ポイント
Embeddedに切り替えても、次のポイントを落とすと「画面が出ない」「画面は出るが戻らない」に近い症状が残ることがあります。
| ポイント | ありがちな症状 | 対処 |
|---|---|---|
WithParentActivityOrWindow の未指定 | UIが表示されない/表示はされるが戻れない | ログイン呼び出し元の Activity(多くは MainActivity)を渡す |
| 非UIスレッドから呼んでいる | 例外が出たり、画面表示が不安定 | ボタンイベント等のUIコンテキストで呼ぶ/必要ならMainThreadへ切り替え |
| 連打や二重起動 | 同時に複数の認証が走り、戻りが崩れる | サインイン中はボタンを無効化し、二重起動を防ぐ |
| 例外処理が不足 | ユーザーには無反応に見える | MsalUiRequiredException とキャンセル(MsalClientException)を分けて扱う |
システムブラウザ方式に戻したい場合のチェックリスト
Embeddedで一旦解決しても、要件上「システムブラウザ(外部ユーザーエージェント)に戻したい」ことはあります。その場合は、次のチェックを順に潰すと原因に当たりやすいです。
- MSAL のバージョンを最新に近いものへ更新(古いバージョンはAndroidの仕様変更に追従できず、戻り周りで詰まりがち)
- リダイレクトURIがアプリ登録(Azure AD / Entra)とアプリ実装で一致しているか
- AndroidManifest に MSAL のコールバック用 Activity(例:BrowserTabActivity)が正しく入っているか
- Android 12以降で必要な
android:exported設定が不足していないか - 端末に Microsoft Authenticator が入っている場合、ブローカー利用のON/OFF(利用するなら署名ハッシュ等の設定が正しいか)
- 既定ブラウザの変更、System WebView / Chrome の更新で再現性が変わるか
「Continueで成功ログは残る」まで到達しているなら、最終的にはAndroidがコールバックIntentをどのActivityに渡すかの問題に帰着しがちです。Embeddedを採用しない方針なら、ここを重点的に見直すと再発防止につながります。
運用で効く:ログを仕込んで“固まったように見える”を減らす
認証はUIとネットワークが絡むため、ユーザーからは「無反応」に見えやすい領域です。再発時に早く原因に辿り着くために、MSALのログとアプリ側の状態ログを仕込んでおくと効果があります。
var pca = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.WithRedirectUri(redirectUri)
.WithLogging((level, message, containsPii) =>
{
// containsPii が true のログは本番では避ける
Android.Util.Log.Info("MSAL", $"{level}: {message}");
},
enablePiiLogging: false,
logLevel: LogLevel.Info,
enableDefaultPlatformLogging: true)
.Build();
さらに、サインインボタン押下から完了までの間は、スピナー表示やボタン無効化を入れるだけでも「固まった」と感じる問い合わせが減ります。UI改善は地味ですが、認証まわりではコスト対効果が高いです。
よくある質問
Embedded WebView を使うと、確認ダイアログは必ず出なくなりますか?
多くのケースで表示されなくなりますが、テナントのポリシーや端末状態によっては別の追加確認が出る可能性はあります。ただし、少なくとも「外部へ出て戻る」経路が短くなるため、Continue/Cancelを押しても戻らないタイプの不具合は解消しやすいです。
シングルテナントでも authority はどう書くべき?
シングルテナントなら、tenant 固定の authority を使うと切り分けが楽です(意図しないアカウント選択やホームテナント混入を減らせます)。ただしB2Bや将来のマルチテナント化の予定がある場合は、設計方針に合わせて決めてください。
「成功しているのにアプリが戻らない」時、ユーザーには何が起きている?
ユーザー視点では認証画面のダイアログが残り続け、アプリは待ち状態です。裏側では、トークン発行やセッション更新が成功していても、アプリが受け取るべきリダイレクトが別タスクに飛んだ/受け口が見つからないなどで、MSALの待機が解けない状況が起き得ます。
まとめ:まず止血するなら Embedded、根本対応は戻り経路の整備
「Are you trying to sign in to {アプリ名}?」の確認ダイアログで Continue/Cancel が効かないように見える問題は、サインインログ上は成功していることが多く、原因は外部ユーザーエージェントからアプリへ戻る経路にあります。
すぐに解消したい場合は、.WithUseEmbeddedWebView(true) を追加し、.WithParentActivityOrWindow(...) を確実に渡して、フローをアプリ内に閉じるのが有効です。要件によりシステムブラウザ方式を維持したい場合は、リダイレクトURI、Manifest、Androidの仕様変更(exported等)を中心に、戻り経路を整えるのが近道になります。

コメント