MsalUiRequiredException during silent token acquisition in Xamarin.Forms
0 reputation · 07 Jul 2020, 03:49 UTC
0 reputation · 07 Jul 2020, 03:49 UTC
In a Xamarin.Forms application utilizing MSAL.NET for Azure AD authentication, the application is designed to prioritize silent token acquisition to maintain a seamless user experience. The goal is to implement a least-privilege security model where tokens are refreshed automatically until they expire or are revoked.
While SecureStorage handles the persistence of the token cache, it does not provide native logic to detect when a refresh token has expired or been invalidated by the identity provider. When AcquireTokenSilent fails, the library throws an MsalUiRequiredException, signaling that interactive intervention is necessary.
There is uncertainty regarding the optimal trigger for transitioning from silent to interactive authentication without creating a disruptive user experience, especially when dealing with revoked sessions versus simple expiration.
MsalUiRequiredException catch block?35275 reputation · 07 Jul 2020, 14:19 UTC
You don't need to detect credential expiration yourself — MsalUiRequiredException is the detection. MSAL throws it only when the identity provider says interaction is required (expired or revoked refresh token, consent needed, conditional access, password change). Transient problems surface as different exception types, so the catch block itself is the correct trigger for interactive login. And you generally should not wipe the whole token cache before going interactive — remove only the affected account, and only when you have reason to believe the session is revoked.
Separate the failure modes by exception type, not by message text:
MsalUiRequiredException — permanent for silent purposes. The server rejected the refresh token or demands interaction. Fall back to AcquireTokenInteractive.MsalServiceException with codes like temporarily_unavailable or service_not_available — the service is unreachable or degraded. The cached refresh token is probably still valid; retry silently later.MsalClientException or an underlying HttpRequestException — local connectivity problems. Check Xamarin.Essentials.Connectivity and retry when the network returns.A practical pattern:
try
{
return await pca.AcquireTokenSilent(scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Genuine need for interaction — go interactive (see below).
return await AcquireInteractiveAsync();
}
catch (MsalServiceException ex) when (
ex.ErrorCode == "temporarily_unavailable" ||
ex.ErrorCode == "service_not_available")
{
// Transient: back off and retry silent; do NOT prompt the user.
await Task.Delay(TimeSpan.FromSeconds(5));
return await pca.AcquireTokenSilent(scopes, account).ExecuteAsync();
}
catch (HttpRequestException)
{
// Offline: surface a "no connection" state, retry on ConnectivityChanged.
throw;
}
Do not retry silent acquisition in a tight loop, and do not treat every failure as a reason to pop a login UI — that is what creates the disruptive experience you're trying to avoid.
Clearing the entire cache signs out every account and is usually unnecessary. MSAL already knows the refresh token is dead — that's why it threw. The interactive flow will simply establish a new session. The scoped cleanup, when you want it, is removing just the affected account:
private async Task<AuthenticationResult> AcquireInteractiveAsync()
{
var accounts = await pca.GetAccountsAsync();
var account = accounts.FirstOrDefault();
if (account != null)
await pca.RemoveAsync(account); // scoped sign-out of the stale account
return await pca.AcquireTokenInteractive(scopes)
.WithParentActivityOrWindow(ParentWindow)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
Notes on the interactive call: it must run on the UI thread with a valid parent activity/window (on Android, an activity reference), or it fails for reasons unrelated to token state. Prompt.SelectAccount or WithLoginHint smooths the flow; Prompt.ForceLogin on every failure degrades UX and is rarely justified.
This assumes MSAL.NET 4.x with IPublicClientApplication and a custom cache serialization helper persisting to SecureStorage. Two caveats: exception subtypes and error codes are version-sensitive, so confirm against the MSAL.NET version in your NuGet packages; and SecureStorage is not the recommended cache backing on every platform — MSAL's own keychain/keystore persistence differs per platform and across releases.
Also note that not every MsalUiRequiredException means revocation — conditional access or MFA policy changes can trigger it with a still-valid refresh token, and only the interactive flow can satisfy those.
Verify both paths in your environment: revoke the user's session in the Azure portal and confirm the app catches the exception and completes interactive login; then toggle airplane mode and confirm no MsalUiRequiredException appears and silent acquisition succeeds after reconnecting. One diagnostic detail worth logging now: the concrete exception type and ErrorCode on every silent failure — it will confirm this classification holds for your tenant's policies.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.