Summary
Add Entra ID support for standalone Blazor WebAssembly applications.
The current Entra ID scaffolder accepts a standalone blazorwasm project and reports success, but it doesn't generate any authentication code or configuration. It also creates an inappropriate secret-bearing app registration.
Standalone Blazor WebAssembly is a public SPA client and requires a different implementation from a Blazor Web App using Interactive WebAssembly.
Related: #2890
Steps to reproduce
dotnet new blazorwasm -n EntraStandaloneWasm `
--framework net11.0
dotnet scaffold aspnet entra-id `
--project .\EntraStandaloneWasm\EntraStandaloneWasm.csproj `
--username <tenant-user> `
--tenantId <tenant> `
--use-existing-application false
dotnet build .\EntraStandaloneWasm\EntraStandaloneWasm.csproj
dotnet run --project .\EntraStandaloneWasm\EntraStandaloneWasm.csproj
Current behavior
The scaffolder creates an Entra app registration and password credential, then attempts to create:
<project-file>.csproj/appsettings.json
The log reports:
Creating new appsettings.json file at
...\EntraStandaloneWasm.csproj\appsettings.json
Failed to write appsettings.json:
Could not find a part of the path ...
The command still returns exit code 0, as tracked separately by #3845.
Because the settings step fails, the remaining package and code-modification steps don't run. The project is left unchanged:
- No
Microsoft.Authentication.WebAssembly.Msal package.
- No
AddMsalAuthentication.
- No
AzureAd configuration under wwwroot.
- No authentication callback component.
- No
AuthorizeRouteView.
- No login/logout UI.
The scaffolded .NET 11 app still builds and runs, but it remains anonymous:
Home
Counter
Weather
About
Hello, world!
Welcome to your new app.
No requests are made to Entra.
The temporary registration created during testing had:
signInAudience: AzureADandPersonalMicrosoftAccount
Web redirect URIs: none
SPA redirect URIs: none
Implicit ID-token issuance: enabled
Password credentials: 1
A standalone WebAssembly app can't protect or use a client secret. The registration is unusable because it has no SPA callback URI.
Two separate scaffold runs created two orphaned registrations because the client ID couldn't be written back to either project.
Expected behavior
When the selected project is a standalone Blazor WebAssembly app, generate public SPA client authentication using MSAL.
Project detection
Detect the standalone client directly from the project SDK:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
and/or its evaluated SDK and Microsoft.AspNetCore.Components.WebAssembly package reference.
The current detector only examines projects referenced by a server project:
dotnet reference list --project <project>
A standalone project normally has no project references, so this path always records IsBlazorWasmProject = false.
Detection should distinguish:
- Standalone Blazor WebAssembly: the selected project itself uses
Microsoft.NET.Sdk.BlazorWebAssembly.
- Blazor Web App Interactive WebAssembly/Auto: a server project references a
.Client WebAssembly project.
These scenarios require different authentication architectures.
Packages
Add:
<PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" />
Don't add:
<PackageReference Include="Microsoft.Identity.Web" />
A standalone browser application has no confidential server host.
Program.cs
Generate:
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind(
"AzureAd",
options.ProviderOptions.Authentication);
});
If API scopes are selected, add them to DefaultAccessTokenScopes and configure the appropriate authorized HTTP client/message handler.
Development configuration
Write the public development registration values to:
wwwroot/appsettings.Development.json
For example:
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/<tenant-id>",
"ClientId": "<development-client-id>",
"ValidateAuthority": true
}
}
No secret or other sensitive value may be placed in a standalone WebAssembly project because all browser-delivered files and assemblies are visible to users.
Authentication components
Generate the standard standalone WebAssembly authentication surface:
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />
@code {
[Parameter]
public string? Action { get; set; }
}
Use AuthorizeRouteView with unauthenticated redirection:
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated != true)
{
<RedirectToLogin />
}
else
{
<p>You aren't authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
</CascadingAuthenticationState>
Generate login/logout UI using the WebAssembly authentication APIs:
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity?.Name!
<button @onclick="BeginLogOut">Log out</button>
</Authorized>
<NotAuthorized>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code {
private void BeginLogOut() =>
Navigation.NavigateToLogout("authentication/logout");
}
Don't generate server-only MapLoginAndLogout() endpoints or form-post logout UI.
App registration
Provision a public SPA registration:
The registration should be clearly identified as development-only and preserve unrelated values when an existing SPA registration is selected.
Runtime behavior
After scaffolding:
- The project builds.
- The navigation displays Log in.
- Login redirects to Entra and returns through
/authentication/login-callback.
- Authentication state is available in client components.
[Authorize], roles, and policies work through AuthorizeRouteView.
- Logout uses the MSAL browser flow.
- No secret is generated or shipped to the browser.
- A failed scaffold run cleans up a newly created registration.
Template parity and guidance
The generated output should preserve the standalone Microsoft identity platform behavior currently provided by:
dotnet new blazorwasm `
--auth SingleOrg `
--client-id <client-id> `
--tenant-id <tenant-id>
References:
Summary
Add Entra ID support for standalone Blazor WebAssembly applications.
The current Entra ID scaffolder accepts a standalone
blazorwasmproject and reports success, but it doesn't generate any authentication code or configuration. It also creates an inappropriate secret-bearing app registration.Standalone Blazor WebAssembly is a public SPA client and requires a different implementation from a Blazor Web App using Interactive WebAssembly.
Related: #2890
Steps to reproduce
Current behavior
The scaffolder creates an Entra app registration and password credential, then attempts to create:
The log reports:
The command still returns exit code
0, as tracked separately by #3845.Because the settings step fails, the remaining package and code-modification steps don't run. The project is left unchanged:
Microsoft.Authentication.WebAssembly.Msalpackage.AddMsalAuthentication.AzureAdconfiguration underwwwroot.AuthorizeRouteView.The scaffolded .NET 11 app still builds and runs, but it remains anonymous:
No requests are made to Entra.
The temporary registration created during testing had:
A standalone WebAssembly app can't protect or use a client secret. The registration is unusable because it has no SPA callback URI.
Two separate scaffold runs created two orphaned registrations because the client ID couldn't be written back to either project.
Expected behavior
When the selected project is a standalone Blazor WebAssembly app, generate public SPA client authentication using MSAL.
Project detection
Detect the standalone client directly from the project SDK:
and/or its evaluated SDK and
Microsoft.AspNetCore.Components.WebAssemblypackage reference.The current detector only examines projects referenced by a server project:
A standalone project normally has no project references, so this path always records
IsBlazorWasmProject = false.Detection should distinguish:
Microsoft.NET.Sdk.BlazorWebAssembly..ClientWebAssembly project.These scenarios require different authentication architectures.
Packages
Add:
Don't add:
A standalone browser application has no confidential server host.
Program.cs
Generate:
If API scopes are selected, add them to
DefaultAccessTokenScopesand configure the appropriate authorized HTTP client/message handler.Development configuration
Write the public development registration values to:
For example:
{ "AzureAd": { "Authority": "https://login.microsoftonline.com/<tenant-id>", "ClientId": "<development-client-id>", "ValidateAuthority": true } }No secret or other sensitive value may be placed in a standalone WebAssembly project because all browser-delivered files and assemblies are visible to users.
Authentication components
Generate the standard standalone WebAssembly authentication surface:
Use
AuthorizeRouteViewwith unauthenticated redirection:Generate login/logout UI using the WebAssembly authentication APIs:
Don't generate server-only
MapLoginAndLogout()endpoints or form-post logout UI.App registration
Provision a public SPA registration:
Platform: Single-page application.
Redirect URI:
Supported account type: single tenant by default, consistent with [Entra ID] Create a single-tenant app registration by default for an explicitly selected tenant #3844.
Authorization code flow with PKCE through MSAL.
Implicit access-token issuance disabled.
Implicit ID-token issuance disabled.
No client secret, certificate, managed identity credential, or Web redirect URI.
The registration should be clearly identified as development-only and preserve unrelated values when an existing SPA registration is selected.
Runtime behavior
After scaffolding:
/authentication/login-callback.[Authorize], roles, and policies work throughAuthorizeRouteView.Template parity and guidance
The generated output should preserve the standalone Microsoft identity platform behavior currently provided by:
References: