dotnet-blazor-auth
Authentication and authorization across all Blazor hosting models. Covers AuthorizeView, CascadingAuthenticationState,
Identity UI scaffolding, role/policy-based authorization, per-hosting-model auth flow differences (cookie vs token), and
external identity providers.
Scope
- Auth flow per Blazor hosting model (Server, WASM, Auto, SSR, Hybrid)
- AuthorizeView and CascadingAuthenticationState patterns
- Identity UI scaffolding and customization
- Role/policy-based authorization in Blazor
- Client-side token handling and external identity providers
- Explicit login/logout/auth UI implementation tasks for Blazor apps
Out of scope
- JWT token generation and validation -- see [skill:dotnet-api-security]
- OWASP security principles -- see [skill:dotnet-security-owasp]
- CSRF/XSS/CSP/rate-limiting hardening without auth-flow work -- see [skill:dotnet-security-owasp]
- Hardening-only reviews of existing login pages without auth-flow implementation changes -- see
[skill:dotnet-security-owasp]
- bUnit testing of auth components -- see [skill:dotnet-blazor-testing]
- E2E auth testing -- see [skill:dotnet-playwright]
- UI framework selection -- see [skill:dotnet-ui-chooser]
Cross-references: [skill:dotnet-api-security] for API-level auth, [skill:dotnet-security-owasp] for OWASP principles,
[skill:dotnet-blazor-patterns] for hosting models, [skill:dotnet-blazor-components] for component architecture,
[skill:dotnet-blazor-testing] for bUnit testing, [skill:dotnet-playwright] for E2E testing, [skill:dotnet-ui-chooser]
for framework selection.
Routing note: do not load this skill for OWASP hardening reviews unless the task explicitly includes Blazor auth flow/UI
implementation.
Auth Flow per Hosting Model
Authentication patterns differ significantly across Blazor hosting models:
| Concern |
InteractiveServer |
InteractiveWebAssembly |
InteractiveAuto |
Static SSR |
Hybrid |
| Auth mechanism |
Cookie-based (server-side) |
Token-based (JWT/OIDC) |
Cookie (Server phase), Token (WASM phase) |
Cookie-based (standard ASP.NET Core) |
Platform-native or cookie |
| User state access |
Direct HttpContext access |
AuthenticationStateProvider |
Varies by phase |
HttpContext |
Platform auth APIs |
| Token storage |
Not needed (cookie) |
localStorage or sessionStorage |
Transition from cookie to token |
Not needed (cookie) |
Secure storage (Keychain, etc.) |
| Refresh handling |
Circuit reconnection |
Token refresh via interceptor |
Automatic |
Standard cookie renewal |
Platform-specific |
InteractiveServer Auth
Server-side Blazor uses cookie authentication. The user authenticates via a standard ASP.NET Core login flow, and the
cookie is sent with the initial HTTP request that establishes the SignalR circuit.
// Program.cs
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Account/AccessDenied";
});
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthorization();
```text
**Gotcha:** `HttpContext` is available during the initial HTTP request but is `null` inside interactive components after
the SignalR circuit is established. Do not access `HttpContext` in interactive component lifecycle methods. Use
`AuthenticationStateProvider` instead.
### InteractiveWebAssembly Auth
WASM runs in the browser. Cookie auth works for same-origin APIs (and Backend-for-Frontend / BFF patterns), but
token-based auth (OIDC/JWT) is the standard approach for cross-origin APIs and delegated access scenarios:
```csharp
// Client Program.cs (WASM)
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = "https://login.example.com";
options.ProviderOptions.ClientId = "blazor-wasm-client";
options.ProviderOptions.ResponseType = "code";
options.ProviderOptions.DefaultScopes.Add("api");
});
```text
```csharp
// Attach tokens to API calls using BaseAddressAuthorizationMessageHandler
// (auto-attaches tokens for requests to the app's base address)
builder.Services.AddHttpClient("API", client =>
client.BaseAddress = new Uri("https://api.example.com"))
.AddHttpMessageHandler(sp =>
sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: ["https://api.example.com"],
scopes: ["api"]));
builder.Services.AddScoped(sp =>
sp.GetRequiredService<IHttpClientFactory>().CreateClient("API"));
```text
### InteractiveAuto Auth
Auto mode starts as InteractiveServer (cookie auth), then transitions to WASM (token auth). Handle both:
```csharp
// Server Program.cs
builder.Services.AddAuthentication()
.AddCookie()
.AddJwtBearer(); // For WASM API calls after transition
builder.Services.AddCascadingAuthenticationState();
```text
### Hybrid (MAUI) Auth
```csharp
// Register platform-specific auth
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, MauiAuthStateProvider>();
// Custom provider using secure storage
public class MauiAuthStateProvider : AuthenticationStateProvider
{
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await SecureStorage.Default.GetAsync("auth_token");
if (string.IsNullOrEmpty(token))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
var claims = ParseClaimsFromJwt(token);
var identity = new ClaimsIdentity(claims, "jwt");
return new AuthenticationState(new ClaimsPrincipal(identity));
}
}
```text
---
## AuthorizeView
`AuthorizeView` conditionally renders content based on the user's authentication and authorization state.
### Basic Usage
```razor
<AuthorizeView>
<Authorized>
<p>Welcome, @context.User.Identity?.Name!</p>
<a href="/Account/Logout">Log out</a>
</Authorized>
<NotAuthorized>
<a href="/Account/Login">Log in</a>
</NotAuthorized>
<Authorizing>
<p>Checking authentication...</p>
</Authorizing>
</AuthorizeView>
```text
### Role-Based
```razor
<AuthorizeView Roles="Admin,Manager">
<Authorized>
<AdminDashboard />
</Authorized>
<NotAuthorized>
<p>You do not have access to the admin dashboard.</p>
</NotAuthorized>
</AuthorizeView>
```text
### Policy-Based
```razor
<AuthorizeView Policy="CanEditProducts">
<Authorized>
<button @onclick="EditProduct">Edit</button>
</Authorized>
</AuthorizeView>
```text
```csharp
// Register policy in Program.cs
builder.Services.AddAuthorizationBuilder()
.AddPolicy("CanEditProducts", policy =>
policy.RequireClaim("permission", "products.edit"));
```csharp
---
## CascadingAuthenticationState
`CascadingAuthenticationState` provides the current `AuthenticationState` as a cascading parameter to all descendant
components.
### Setup
```csharp
// Program.cs -- register cascading auth state
builder.Services.AddCascadingAuthenticationState();
```csharp
This replaces wrapping the entire app in `<CascadingAuthenticationState>` (the older pattern). The service-based
registration (.NET 8+) is preferred.
### Consuming Auth State in Components
```razor
@code {
[CascadingParameter]
private Task<AuthenticationState>? AuthState { get; set; }
private string? userName;
protected override async Task OnInitializedAsync()
{
if (AuthState is not null)
{
var state = await AuthState;
userName = state.User.Identity?.Name;
}
}
}
```text
### Accessing Claims
```csharp
var state = await AuthState;
var user = state.User;
// Check authentication
if (user.Identity?.IsAuthenticated == true)
{
var email = user.FindFirst(ClaimTypes.Email)?.Value;
var roles = user.FindAll(ClaimTypes.Role).Select(c => c.Value);
var isAdmin = user.IsInRole("Admin");
}
```text
---
## Identity UI Scaffolding
ASP.NET Core Identity provides a complete authentication system with registration, login, email confirmation, password
reset, and two-factor authentication.
### Adding Identity to a Blazor Web App
```bash
# Add Identity scaffolding
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
```bash
```csharp
// Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
```text
### Scaffolding Identity Pages
```bash
# Scaffold individual Identity pages for customization
dotnet aspnet-codegenerator identity -dc ApplicationDbContext --files "Account.Login;Account.Register;Account.Logout"
```bash
### Custom Identity UI with Blazor Components
For a fully Blazor-native auth experience, create Blazor components that call Identity APIs:
```razor
@page "/Account/Login"
@inject SignInManager<ApplicationUser> SignInManager
@inject NavigationManager Navigation
<EditForm Model="loginModel" FormName="login" Enhance>
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<InputText @bind-Value="loginModel.Email" placeholder="Email" />
</div>
<div>
<InputText @bind-Value="loginModel.Password" type="password" placeholder="Password" />
</div>
<div>
<InputCheckbox @bind-Value="loginModel.RememberMe" /> Remember me
</div>
<button type="submit">Log in</button>
</EditForm>
@if (!string.IsNullOrEmpty(errorMessage))
{
<p class="text-danger">@errorMessage</p>
}
@code {
[SupplyParameterFromForm]
private LoginModel loginModel { get; set; } = new();
private string? errorMessage;
private async Task HandleLogin()
{
var result = await SignInManager.PasswordSignInAsync(
loginModel.Email, loginModel.Password,
loginModel.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
Navigation.NavigateTo("/", forceLoad: true);
}
else if (result.RequiresTwoFactor)
{
Navigation.NavigateTo("/Account/LoginWith2fa");
}
else if (result.IsLockedOut)
{
errorMessage = "Account is locked. Try again later.";
}
else
{
errorMessage = "Invalid login attempt.";
}
}
}
```text
**Gotcha:** `SignInManager` uses `HttpContext` to set cookies. In Interactive render modes, `HttpContext` is not
available after the circuit is established. Login/logout pages must use Static SSR (no `@rendermode`) so they have
## Detailed Examples
See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.
1---2name: dotnet-blazor-auth3category: web4subcategory: blazor5description: Implements Blazor auth flows -- login/logout, AuthorizeView, Identity UI, OIDC.6license: MIT7targets: ['*']8tags: [ui, dotnet, skill]9version: '0.0.1'10author: 'dotnet-agent-harness'11invocable: true12claudecode:13 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']14codexcli:15 short-description: '.NET skill guidance for ui tasks'16opencode:17 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']18copilot: {}19geminicli: {}20antigravity: {}21---2223# dotnet-blazor-auth2425Authentication and authorization across all Blazor hosting models. Covers AuthorizeView, CascadingAuthenticationState,26Identity UI scaffolding, role/policy-based authorization, per-hosting-model auth flow differences (cookie vs token), and27external identity providers.2829## Scope3031- Auth flow per Blazor hosting model (Server, WASM, Auto, SSR, Hybrid)32- AuthorizeView and CascadingAuthenticationState patterns33- Identity UI scaffolding and customization34- Role/policy-based authorization in Blazor35- Client-side token handling and external identity providers36- Explicit login/logout/auth UI implementation tasks for Blazor apps3738## Out of scope3940- JWT token generation and validation -- see [skill:dotnet-api-security]41- OWASP security principles -- see [skill:dotnet-security-owasp]42- CSRF/XSS/CSP/rate-limiting hardening without auth-flow work -- see [skill:dotnet-security-owasp]43- Hardening-only reviews of existing login pages without auth-flow implementation changes -- see44 [skill:dotnet-security-owasp]45- bUnit testing of auth components -- see [skill:dotnet-blazor-testing]46- E2E auth testing -- see [skill:dotnet-playwright]47- UI framework selection -- see [skill:dotnet-ui-chooser]4849Cross-references: [skill:dotnet-api-security] for API-level auth, [skill:dotnet-security-owasp] for OWASP principles,50[skill:dotnet-blazor-patterns] for hosting models, [skill:dotnet-blazor-components] for component architecture,51[skill:dotnet-blazor-testing] for bUnit testing, [skill:dotnet-playwright] for E2E testing, [skill:dotnet-ui-chooser]52for framework selection.5354Routing note: do not load this skill for OWASP hardening reviews unless the task explicitly includes Blazor auth flow/UI55implementation.5657---5859## Auth Flow per Hosting Model6061Authentication patterns differ significantly across Blazor hosting models:6263| Concern | InteractiveServer | InteractiveWebAssembly | InteractiveAuto | Static SSR | Hybrid |64| ----------------- | --------------------------- | ---------------------------------- | ----------------------------------------- | ------------------------------------ | ------------------------------- |65| Auth mechanism | Cookie-based (server-side) | Token-based (JWT/OIDC) | Cookie (Server phase), Token (WASM phase) | Cookie-based (standard ASP.NET Core) | Platform-native or cookie |66| User state access | Direct `HttpContext` access | `AuthenticationStateProvider` | Varies by phase | `HttpContext` | Platform auth APIs |67| Token storage | Not needed (cookie) | `localStorage` or `sessionStorage` | Transition from cookie to token | Not needed (cookie) | Secure storage (Keychain, etc.) |68| Refresh handling | Circuit reconnection | Token refresh via interceptor | Automatic | Standard cookie renewal | Platform-specific |6970### InteractiveServer Auth7172Server-side Blazor uses cookie authentication. The user authenticates via a standard ASP.NET Core login flow, and the73cookie is sent with the initial HTTP request that establishes the SignalR circuit.7475````csharp7677// Program.cs78builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)79 .AddCookie(options =>80 {81 options.LoginPath = "/Account/Login";82 options.AccessDeniedPath = "/Account/AccessDenied";83 });8485builder.Services.AddCascadingAuthenticationState();86builder.Services.AddAuthorization();8788```text8990**Gotcha:** `HttpContext` is available during the initial HTTP request but is `null` inside interactive components after91the SignalR circuit is established. Do not access `HttpContext` in interactive component lifecycle methods. Use92`AuthenticationStateProvider` instead.9394### InteractiveWebAssembly Auth9596WASM runs in the browser. Cookie auth works for same-origin APIs (and Backend-for-Frontend / BFF patterns), but97token-based auth (OIDC/JWT) is the standard approach for cross-origin APIs and delegated access scenarios:9899```csharp100101// Client Program.cs (WASM)102builder.Services.AddOidcAuthentication(options =>103{104 options.ProviderOptions.Authority = "https://login.example.com";105 options.ProviderOptions.ClientId = "blazor-wasm-client";106 options.ProviderOptions.ResponseType = "code";107 options.ProviderOptions.DefaultScopes.Add("api");108});109110```text111112```csharp113114// Attach tokens to API calls using BaseAddressAuthorizationMessageHandler115// (auto-attaches tokens for requests to the app's base address)116builder.Services.AddHttpClient("API", client =>117 client.BaseAddress = new Uri("https://api.example.com"))118 .AddHttpMessageHandler(sp =>119 sp.GetRequiredService<AuthorizationMessageHandler>()120 .ConfigureHandler(121 authorizedUrls: ["https://api.example.com"],122 scopes: ["api"]));123124builder.Services.AddScoped(sp =>125 sp.GetRequiredService<IHttpClientFactory>().CreateClient("API"));126127```text128129### InteractiveAuto Auth130131Auto mode starts as InteractiveServer (cookie auth), then transitions to WASM (token auth). Handle both:132133```csharp134135// Server Program.cs136builder.Services.AddAuthentication()137 .AddCookie()138 .AddJwtBearer(); // For WASM API calls after transition139140builder.Services.AddCascadingAuthenticationState();141142```text143144### Hybrid (MAUI) Auth145146```csharp147148// Register platform-specific auth149builder.Services.AddAuthorizationCore();150builder.Services.AddScoped<AuthenticationStateProvider, MauiAuthStateProvider>();151152// Custom provider using secure storage153public class MauiAuthStateProvider : AuthenticationStateProvider154{155 public override async Task<AuthenticationState> GetAuthenticationStateAsync()156 {157 var token = await SecureStorage.Default.GetAsync("auth_token");158 if (string.IsNullOrEmpty(token))159 {160 return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));161 }162163 var claims = ParseClaimsFromJwt(token);164 var identity = new ClaimsIdentity(claims, "jwt");165 return new AuthenticationState(new ClaimsPrincipal(identity));166 }167}168169```text170171---172173## AuthorizeView174175`AuthorizeView` conditionally renders content based on the user's authentication and authorization state.176177### Basic Usage178179```razor180181<AuthorizeView>182 <Authorized>183 <p>Welcome, @context.User.Identity?.Name!</p>184 <a href="/Account/Logout">Log out</a>185 </Authorized>186 <NotAuthorized>187 <a href="/Account/Login">Log in</a>188 </NotAuthorized>189 <Authorizing>190 <p>Checking authentication...</p>191 </Authorizing>192</AuthorizeView>193194```text195196### Role-Based197198```razor199200<AuthorizeView Roles="Admin,Manager">201 <Authorized>202 <AdminDashboard />203 </Authorized>204 <NotAuthorized>205 <p>You do not have access to the admin dashboard.</p>206 </NotAuthorized>207</AuthorizeView>208209```text210211### Policy-Based212213```razor214215<AuthorizeView Policy="CanEditProducts">216 <Authorized>217 <button @onclick="EditProduct">Edit</button>218 </Authorized>219</AuthorizeView>220221```text222223```csharp224225// Register policy in Program.cs226builder.Services.AddAuthorizationBuilder()227 .AddPolicy("CanEditProducts", policy =>228 policy.RequireClaim("permission", "products.edit"));229230```csharp231232---233234## CascadingAuthenticationState235236`CascadingAuthenticationState` provides the current `AuthenticationState` as a cascading parameter to all descendant237components.238239### Setup240241```csharp242243// Program.cs -- register cascading auth state244builder.Services.AddCascadingAuthenticationState();245246```csharp247248This replaces wrapping the entire app in `<CascadingAuthenticationState>` (the older pattern). The service-based249registration (.NET 8+) is preferred.250251### Consuming Auth State in Components252253```razor254255@code {256 [CascadingParameter]257 private Task<AuthenticationState>? AuthState { get; set; }258259 private string? userName;260261 protected override async Task OnInitializedAsync()262 {263 if (AuthState is not null)264 {265 var state = await AuthState;266 userName = state.User.Identity?.Name;267 }268 }269}270271```text272273### Accessing Claims274275```csharp276277var state = await AuthState;278var user = state.User;279280// Check authentication281if (user.Identity?.IsAuthenticated == true)282{283 var email = user.FindFirst(ClaimTypes.Email)?.Value;284 var roles = user.FindAll(ClaimTypes.Role).Select(c => c.Value);285 var isAdmin = user.IsInRole("Admin");286}287288```text289290---291292## Identity UI Scaffolding293294ASP.NET Core Identity provides a complete authentication system with registration, login, email confirmation, password295reset, and two-factor authentication.296297### Adding Identity to a Blazor Web App298299```bash300301# Add Identity scaffolding302dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore303dotnet add package Microsoft.AspNetCore.Identity.UI304305```bash306307```csharp308309// Program.cs310builder.Services.AddDbContext<ApplicationDbContext>(options =>311 options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));312313builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>314{315 options.Password.RequireDigit = true;316 options.Password.RequiredLength = 8;317 options.Password.RequireNonAlphanumeric = true;318 options.SignIn.RequireConfirmedAccount = true;319})320.AddEntityFrameworkStores<ApplicationDbContext>()321.AddDefaultTokenProviders();322323```text324325### Scaffolding Identity Pages326327```bash328329# Scaffold individual Identity pages for customization330dotnet aspnet-codegenerator identity -dc ApplicationDbContext --files "Account.Login;Account.Register;Account.Logout"331332```bash333334### Custom Identity UI with Blazor Components335336For a fully Blazor-native auth experience, create Blazor components that call Identity APIs:337338```razor339340@page "/Account/Login"341@inject SignInManager<ApplicationUser> SignInManager342@inject NavigationManager Navigation343344<EditForm Model="loginModel" OnValidSubmit="HandleLogin" FormName="login" Enhance>345 <DataAnnotationsValidator />346 <ValidationSummary />347348 <div>349 <InputText @bind-Value="loginModel.Email" placeholder="Email" />350 </div>351 <div>352 <InputText @bind-Value="loginModel.Password" type="password" placeholder="Password" />353 </div>354 <div>355 <InputCheckbox @bind-Value="loginModel.RememberMe" /> Remember me356 </div>357358 <button type="submit">Log in</button>359</EditForm>360361@if (!string.IsNullOrEmpty(errorMessage))362{363 <p class="text-danger">@errorMessage</p>364}365366@code {367 [SupplyParameterFromForm]368 private LoginModel loginModel { get; set; } = new();369370 private string? errorMessage;371372 private async Task HandleLogin()373 {374 var result = await SignInManager.PasswordSignInAsync(375 loginModel.Email, loginModel.Password,376 loginModel.RememberMe, lockoutOnFailure: true);377378 if (result.Succeeded)379 {380 Navigation.NavigateTo("/", forceLoad: true);381 }382 else if (result.RequiresTwoFactor)383 {384 Navigation.NavigateTo("/Account/LoginWith2fa");385 }386 else if (result.IsLockedOut)387 {388 errorMessage = "Account is locked. Try again later.";389 }390 else391 {392 errorMessage = "Invalid login attempt.";393 }394 }395}396397```text398399**Gotcha:** `SignInManager` uses `HttpContext` to set cookies. In Interactive render modes, `HttpContext` is not400available after the circuit is established. Login/logout pages must use Static SSR (no `@rendermode`) so they have401402## Detailed Examples403404See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.