maui-authentication
.NET MAUI Authentication
Security: Never Embed Secrets
❌ Never embed client secrets, API keys, or signing keys in a mobile app binary. They can be extracted trivially via decompilation.
The correct pattern:
- App calls
WebAuthenticatorpointing to your server endpoint - Server initiates the OAuth flow with the identity provider (holds the client secret)
- Provider redirects back to your server with an auth code
- Server exchanges the code for tokens and returns them to the app via the callback URI
WebAuthenticator Gotchas
⚠️ Windows WebAuthenticator is broken
Windows WebAuthenticator is currently broken. See dotnet/maui#2702. Use MSAL or a WinUI-specific workaround for Windows auth flows.
⚠️ Apple Sign In returns name/email only once
Apple only returns the user's name and email on the first sign-in. Cache them immediately — subsequent sign-ins won't include them.
⚠️ PrefersEphemeralWebBrowserSession
Set to true on iOS 13+ to force a fresh login prompt. When false (default), the auth session shares cookies with Safari — the user may be auto-logged in, which can confuse logout/switch-account flows.
⚠️ Callback URI mismatches
The most common auth failure is a URI scheme mismatch. The CallbackUrl in code must exactly match:
- Android:
DataScheme+DataHostin theIntentFilter - iOS:
CFBundleURLSchemesinInfo.plist - Windows:
Protocol NameinPackage.appxmanifest
WebAuthenticator Checklist
- Callback URI scheme matches across all platform configs and
CallbackUrl - Android has
WebAuthenticatorCallbackActivitywith correctIntentFilter - Android 11+ has
<queries>for Custom Tabs in the manifest - iOS/Mac Catalyst has
CFBundleURLTypesinInfo.plist - Client secrets are on the server, not in the app
- Tokens stored with
SecureStorage, cleared on logout -
TaskCanceledExceptionhandled gracefully in UI
Choosing Between WebAuthenticator and MSAL.NET
| Criteria | WebAuthenticator | MSAL.NET |
|---|---|---|
| Identity provider | Any OAuth 2.0 / OIDC | Microsoft Entra ID |
| Broker support (SSO) | ❌ No | ✅ Microsoft Authenticator, Company Portal |
| Conditional Access / MFA | ❌ Manual | ✅ Built-in |
| Token cache & refresh | ❌ Manual (SecureStorage) | ✅ Automatic |
| Complexity | Simple | More setup |
| Use when | Google, Apple, generic OIDC | Entra ID / Azure AD, Microsoft Graph |
MSAL.NET Gotchas
⚠️ Android: OnActivityResult is required
Forgetting AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs in MainActivity.OnActivityResult causes auth to hang silently after the browser returns.
⚠️ iOS: Keychain sharing is required
Without Entitlements.plist containing keychain group com.microsoft.adalcache, token caching fails silently and users are prompted to sign in every time.
⚠️ Handle MsalUiRequiredException
When AcquireTokenSilent throws MsalUiRequiredException, the cached token is expired and interaction is needed. Always fall back to AcquireTokenInteractive.
// ❌ Ignoring MsalUiRequiredException — user gets a crash
var result = await _pca.AcquireTokenSilent(scopes, account).ExecuteAsync(ct);
// ✅ Fall back to interactive when silent fails
try
{
result = await _pca.AcquireTokenSilent(scopes, account).ExecuteAsync(ct);
}
catch (MsalUiRequiredException)
{
result = await _pca.AcquireTokenInteractive(scopes).ExecuteAsync(ct);
}
⚠️ Handle user cancellation gracefully
// ✅ Don't treat cancellation as an error
catch (MsalClientException ex) when (ex.ErrorCode == "authentication_canceled")
{
return null; // User cancelled — not an error
}
⚠️ Blazor Hybrid: Auth happens at the MAUI layer
In MAUI Blazor Hybrid apps, authentication must happen at the MAUI layer (MSAL.NET), not in the Blazor WebView. Don't use AddMicrosoftIdentityWebApp or server-side OIDC patterns. Instead:
- MAUI handles sign-in via
IAuthService(MSAL.NET) - A custom
MsalAuthenticationStateProviderexposes auth state to Blazor HttpClientwithDelegatingHandlerattaches bearer tokens automatically
MSAL.NET Checklist
-
Microsoft.Identity.ClientNuGet package added - App registration created in Entra ID with correct redirect URIs
-
AuthConfig/appsettings.jsonhas ClientId, TenantId, Scopes - Android:
AndroidManifest.xmlhas<queries>for broker and browsers - Android:
MainActivity.OnActivityResultcallsAuthenticationContinuationHelper - iOS:
Info.plisthasCFBundleURLSchemeswithmsauth.{BundleId} - iOS:
Entitlements.plisthas keychain groupcom.microsoft.adalcache - iOS:
AppDelegate.OpenUrlcallsAuthenticationContinuationHelper -
IAuthServiceregistered as singleton in DI -
DelegatingHandlerattached toHttpClientfor API calls - Login/logout UI wired up
-
MsalUiRequiredExceptionhandled (triggers interactive sign-in) -
MsalClientExceptionwithauthentication_canceledhandled gracefully