Skip to content

Add MauiBlazorWebEntra sample — Entra External ID (CIAM) for .NET 10 MAUI + Blazor Web#1

Open
mattleibow wants to merge 18 commits intomainfrom
dev/maui-blazor-web-entra-10
Open

Add MauiBlazorWebEntra sample — Entra External ID (CIAM) for .NET 10 MAUI + Blazor Web#1
mattleibow wants to merge 18 commits intomainfrom
dev/maui-blazor-web-entra-10

Conversation

@mattleibow
Copy link
Copy Markdown
Owner

New sample: 10.0/MauiBlazorWebEntra

Demonstrates Microsoft Entra External ID (CIAM) authentication for a .NET 10 MAUI Blazor Hybrid app with a shared Blazor Web companion.

Architecture

  • Dual auth on web: OIDC+Cookie for browsers, JWT Bearer for MAUI API calls
  • MSAL on native: platform-specific interactive sign-in with silent token refresh
  • Shared UI: Razor class library with Home, Counter, Weather, and Account pages

Platform support

Platform Auth method Status
Web (Blazor Server) OIDC via Microsoft.Identity.Web
iOS MSAL + system browser
Android MSAL + Chrome Custom Tabs
Windows MSAL + loopback redirect 🔲 (untested, wired up)
Mac Catalyst MSAL + custom ASWebAuthenticationSession WebUI 🔄 (workaround for MSAL #3527)

Key features

  • Setup-Azure.ps1 — Guided 5-step interactive script to create CIAM tenant, register apps, configure user flows
  • Teardown-Azure.ps1 — Clean removal of app registrations and user flows
  • CIAM sign-up via prompt=create deep-link (screen_hint=signup is not supported by CIAM)
  • Account page showing user profile claims
  • Chrome Custom Tabs on Android (requires <queries> manifest entry for API 30+)
  • Mac Catalyst auth via custom ICustomWebUi using ASWebAuthenticationSession since MSAL has no native Mac Catalyst support

Notable workarounds

  • Mac Catalyst: MSAL.NET doesn't ship a maccatalyst TFM — falls back to generic net8.0 assembly which throws PlatformNotSupportedException. Solved with custom MacCatalystWebUi class implementing ICustomWebUi
  • Android Chrome Custom Tabs: Requires <queries> in AndroidManifest.xml for package visibility on API 30+
  • CIAM sign-up: screen_hint=signup is silently ignored — must use prompt=create

Setup

Run Setup-Azure.ps1 for guided configuration, or see README for manual steps.

@mattleibow mattleibow force-pushed the dev/maui-blazor-web-entra-10 branch 2 times, most recently from 800764c to 597b596 Compare March 16, 2026 21:47
mattleibow and others added 12 commits March 24, 2026 16:54
New .NET MAUI Blazor Hybrid + ASP.NET Core Web App sample that replaces
ASP.NET Core Identity with Microsoft Entra External ID (CIAM) for
authentication.

Web server:
- Dual auth via BearerOrCookie policy scheme: OIDC + Cookie for browser
  users, JWT Bearer for MAUI API calls
- Uses Microsoft.Identity.Web instead of EF Core/Identity
- No local user database — Entra manages all accounts
- Login/logout/weather API endpoints

MAUI app:
- MSAL.NET (Microsoft.Identity.Client) for native authentication
- Interactive sign-in via system browser, silent token refresh
- Android: MsalActivity for MSAL redirect URI callback
- iOS: CFBundleURLTypes for MSAL redirect URI scheme

Infrastructure:
- Setup-Azure.ps1: Interactive PowerShell script that creates Entra app
  registrations, exposes API scope, generates client secret, and patches
  all config files with real values
- Teardown-Azure.ps1: Cleanup script to remove app registrations
- README.md with architecture overview and quick start guide

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… setup improvements

- Add Account.razor shared page with two-column claims display
- Add /authentication/register endpoint with prompt=create for CIAM sign-up
- Add Register and Account nav links (Web + MAUI)
- iOS: Add MSAL callback in AppDelegate.cs, keychain security group
- Android: Add OnActivityResult callback, Chrome Custom Tabs <queries> manifest
- Mac Catalyst: Custom ICustomWebUi using ASWebAuthenticationSession (MSAL has no native Mac Catalyst support - issue #3527)
- Upgrade Microsoft.Identity.Client 4.70.0 → 4.83.1
- Rewrite Setup-Azure.ps1 as guided 5-step interactive walkthrough
- Update Teardown-Azure.ps1 to match new setup flow
- Update README quick start section

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add missing Platforms/iOS/Entitlements.plist with keychain-access-groups
- Use $(AppIdentifierPrefix)$(CFBundleIdentifier) instead of hardcoded adalcache group
- Update WithIosKeychainSecurityGroup to match bundle identifier
- Align MacCatalyst entitlements to same pattern

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Convert all .cs files to file-scoped namespaces (C# 10)
- Use primary constructors for MsalAuthenticationStateProvider and WeatherService (C# 12)
- Replace collection initializers with collection expressions (C# 12)
- Remove custom MacCatalystWebUi (ASWebAuthenticationSession workaround)
- Mac Catalyst now uses WithSystemWebViewOptions like iOS
- Remove network.server entitlement (no longer needed without loopback)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On first call to GetAuthenticationStateAsync, attempt a silent token
acquisition using MSAL's cached credentials. This restores the user's
authenticated session automatically when the app restarts, without
requiring an interactive sign-in.

Also converts MsalConfig.cs to file-scoped namespace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Step 3: Check if app registrations exist by display name before
  creating. Reuse existing apps and preserve client secrets when
  appsettings.json already has a real value. Only generate a new
  secret when the config file still has a placeholder.
- Step 4: Check if signup_signin user flow exists before creating.
  If it exists, verify linked apps and add any missing ones.

This allows the script to be safely re-run without duplicating
resources or invalidating secrets on other machines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace credential reset with Graph API addPassword so new secrets
never invalidate existing ones on other machines. When app already
exists, interactively ask whether to keep the current secret, paste
one from another machine, or generate a new one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update PSScriptRoot paths to resolve parent directory and update
README with new script locations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Microsoft.Identity.Client.Desktop.WinUI3 package for embedded WebView2
  auth on Windows (WAM broker doesn't support CIAM tenants)
- Add Microsoft.Identity.Client.Broker package (Windows-only)
- Update MauiVersion to 10.0.50 to fix HybridWebView.js build issue
- Configure WithWindowsDesktopFeatures for embedded WebView2 + WAM broker
- Set http://localhost redirect URI for Windows (CIAM requires explicit URI,
  WithDefaultRedirectUri resolves to nativeclient on MAUI which doesn't work)
- Pass WinUI3 Window object (not IntPtr handle) to WithParentActivityOrWindow
  for embedded WebView2 compatibility
- Move MsalConfig.cs to project root
- Add platform-specific redirect URI via #if WINDOWS in MsalConfig
- Register http://localhost in Setup-Azure.ps1 alongside msal{ClientId}://auth
- Add Windows token cache persistence via SecureStorage (DPAPI-backed) since
  MSAL only persists automatically on iOS (Keychain) and Android (SharedPrefs)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extract MSAL client setup into MsalServiceExtensions.AddMsalClient()
  extension on IServiceCollection, cleaning up MauiProgram.cs
- Extract platform-specific interactive auth config into
  WithPlatformOptions() extension on AcquireTokenInteractiveParameterBuilder,
  removing #if directives from MsalAuthenticationStateProvider
- Extract Windows token cache persistence into private
  EnableSecureStorageTokenCachePersistence() method using MAUI SecureStorage
  (DPAPI-backed); iOS/Android persist natively via Keychain/SharedPreferences
- Add null checks for platform window/activity in WithPlatformOptions()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align iOS keychain-access-groups entitlement with Mac Catalyst to use
com.microsoft.adalcache (MSAL's default shared keychain group). Add
WithIosKeychainSecurityGroup to the MSAL builder for iOS and Mac
Catalyst so tokens persist correctly across app restarts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MSAL.NET doesn't ship a maccatalyst TFM (issue #3527), so the generic
.NET assembly is used at runtime. This causes two problems:

1. AcquireTokenInteractive throws PlatformNotSupportedException because
   the generic assembly has no system browser integration.
2. The token cache is in-memory only — tokens are lost on app restart.

Auth fix: Add MacCatalystWebUi implementing ICustomWebUi, which drives
ASWebAuthenticationSession directly. Wired up via a WithMacCatalystWebView()
extension method on AcquireTokenInteractiveParameterBuilder.

Persistence fix: Enable SecureStorage-based token cache serialization for
Mac Catalyst (same mechanism already used on Windows).

Both workarounds reference MSAL issue #3527 and can be removed once MSAL
ships native Mac Catalyst support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow force-pushed the dev/maui-blazor-web-entra-10 branch from 597b596 to 555df7b Compare March 24, 2026 17:09
mattleibow and others added 6 commits April 17, 2026 17:55
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Workforce Entra ID tokens use 'preferred_username' (UPN) instead of
the CIAM-specific 'emails' claim. Reorder the fallback chain so
preferred_username is checked first, followed by standard 'email'
and ClaimTypes.Email.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The initial copy left the old MauiBlazorWebEntra assembly names in
the MAUI host index.html, causing _content CSS links and the scoped
styles bundle to 404 inside the BlazorWebView. Updated title,
bootstrap, app.css, and .styles.css references to use the new
MauiBlazorWebEntraWorkforce assembly names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Login endpoint now validates returnUrl is a well-formed relative URI
  to prevent open-redirect attacks (GPT 5.4 finding)
- Teardown script checks exit codes after each az ad app delete and
  preserves the setup data file if deletion fails (GPT 5.4 finding)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Setup-Azure.ps1:
- Use exact JMESPath filter for app list queries to avoid prefix-match
  false positives (e.g. matching 'MyApp-Test' when looking for 'MyApp')
- Patch web app redirect URIs on every run (not just creation) so
  re-running against an existing app with stale URIs doesn't silently
  misconfigure OIDC sign-in/sign-out

Teardown-Azure.ps1:
- Add note that config files (appsettings.json, MsalConfig.cs) are not
  reverted to placeholders, matching the original CIAM teardown behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant