dotnet-blazor-auth について
各ホスティングモデルの Blazor アプリで認証と認可を実装するためのガイドです。認証 UI、Identity のスキャフォールディング、ポリシー、Cookie とトークンのフローを扱います。
主な機能
- 01ホスティングモデル別の認証フロー
- 02AuthorizeView と認証状態
- 03Identity UI のスキャフォールディング
- 04ロール・ポリシーによる認可
- 05外部 ID プロバイダー
- 06トークン処理の指針
活用シーン
- 01Blazor アプリにログイン・ログアウト画面を追加
- 02ロールやポリシーでページへのアクセスを制限
- 03Server、WebAssembly、Hybrid の認証を設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-blazor-authインストール後、Claude Code で/dotnet-blazor-authを入力して読み込まれたか確認します。
詳細
dotnet-blazor-auth
すべての Blazor ホスティングモデルにおける認証と認可を扱います。AuthorizeView、CascadingAuthenticationState、Identity UI のスキャフォールディング、ロール/ポリシーベースの認可、ホスティングモデルごとの認証フローの違い(Cookie とトークン)、外部 ID プロバイダーを取り上げます。
対象範囲の境界: このスキルが扱うのは、Blazor 固有の認証 UI パターン(AuthorizeView、CascadingAuthenticationState、Identity UI のスキャフォールディング、クライアント側のトークン処理、ホスティングモデルごとの認証フロー構成)です。API レベルの認証(JWT、OAuth/OIDC、パスキー、CORS、レート制限)については、[skill:dotnet-api-security] を参照してください。
対象外: JWT トークンの生成と検証については、[skill:dotnet-api-security] を参照してください。OWASP のセキュリティ原則については、[skill:dotnet-security-owasp] を参照してください。認証コンポーネントの bUnit テストについては、[skill:dotnet-blazor-testing] を参照してください。E2E 認証テストについては、[skill:dotnet-playwright] を参照してください。UI フレームワークの選定については、[skill:dotnet-ui-chooser] を参照してください。
相互参照:API レベルの認証については [skill:dotnet-api-security]、OWASP の原則については [skill:dotnet-security-owasp]、ホスティングモデルについては [skill:dotnet-blazor-patterns]、コンポーネントアーキテクチャについては [skill:dotnet-blazor-components]、bUnit テストについては [skill:dotnet-blazor-testing]、E2E テストについては [skill:dotnet-playwright]、フレームワークの選定については [skill:dotnet-ui-chooser] を参照してください。
ホスティングモデルごとの認証フロー
認証パターンは Blazor のホスティングモデルによって大きく異なります。
| 懸念事項 | InteractiveServer | InteractiveWebAssembly | InteractiveAuto | Static SSR | Hybrid |
|---|---|---|---|---|---|
| 認証方式 | Cookie ベース(サーバー側) | トークンベース(JWT/OIDC) | Cookie(Server フェーズ)、トークン(WASM フェーズ) | Cookie ベース(標準の ASP.NET Core) | プラットフォームネイティブまたは Cookie |
| ユーザー状態へのアクセス | HttpContext に直接アクセス | AuthenticationStateProvider | フェーズによって異なります | HttpContext | プラットフォームの認証 API |
| トークンの保存先 | 不要(Cookie) | localStorage または sessionStorage | Cookie からトークンへ移行 | 不要(Cookie) | セキュアストレージ(Keychain など) |
| 更新処理 | 回線の再接続 | インターセプターによるトークン更新 | 自動 | 標準の Cookie 更新 | プラットフォーム固有 |
InteractiveServer の認証
サーバーサイド Blazor では Cookie 認証を使用します。ユーザーは標準の ASP.NET Core ログインフローで認証され、SignalR 回線を確立する最初の HTTP リクエストとともに Cookie が送信されます。
// Program.cs
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Account/AccessDenied";
});
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthorization();
注意点: HttpContext は最初の HTTP リクエスト中には利用できますが、SignalR 回線の確立後はインタラクティブコンポーネント内で null になります。インタラクティブコンポーネントのライフサイクルメソッド内で HttpContext にアクセスしないでください。代わりに AuthenticationStateProvider を使用してください。
InteractiveWebAssembly の認証
WASM はブラウザーで実行されます。同一オリジンの API(および Backend-for-Frontend / BFF パターン)では Cookie 認証を使用できますが、クロスオリジン API や委任アクセスのシナリオでは、トークンベース認証(OIDC/JWT)が標準的な方法です。
// 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");
});
// 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"));
InteractiveAuto の認証
Auto モードは InteractiveServer(Cookie 認証)として開始し、その後 WASM(トークン認証)に移行します。両方に対応してください。
// Server Program.cs
builder.Services.AddAuthentication()
.AddCookie()
.AddJwtBearer(); // For WASM API calls after transition
builder.Services.AddCascadingAuthenticationState();
Hybrid(MAUI)の認証
// 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));
}
}
AuthorizeView
AuthorizeView は、ユーザーの認証状態と認可状態に基づいてコンテンツを条件付きで表示します。
基本的な使い方
<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>
ロールベース
<AuthorizeView Roles="Admin,Manager">
<Authorized>
<AdminDashboard />
</Authorized>
<NotAuthorized>
<p>You do not have access to the admin dashboard.</p>
</NotAuthorized>
</AuthorizeView>
ポリシーベース
<AuthorizeView Policy="CanEditProducts">
<Authorized>
<button @onclick="EditProduct">Edit</button>
</Authorized>
</AuthorizeView>
// Register policy in Program.cs
builder.Services.AddAuthorizationBuilder()
.AddPolicy("CanEditProducts", policy =>
policy.RequireClaim("permission", "products.edit"));
CascadingAuthenticationState
CascadingAuthenticationState は、現在の AuthenticationState をカスケードパラメーターとして、すべての子孫コンポーネントに提供します。
セットアップ
// Program.cs -- register cascading auth state
builder.Services.AddCascadingAuthenticationState();
これは、アプリ全体を <CascadingAuthenticationState> でラップする方法(以前のパターン)に代わるものです。サービスベースの登録(.NET 8 以降)が推奨されます。
コンポーネントでの認証状態の利用
@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;
}
}
}
クレームへのアクセス
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");
}
Identity UI のスキャフォールディング
ASP.NET Core Identity は、登録、ログイン、メール確認、パスワードのリセット、二要素認証を備えた、完全な認証システムを提供します。
Blazor Web アプリへの Identity の追加
# Add Identity scaffolding
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
// 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();
Identity ページのスキャフォールディング
# Scaffold individual Identity pages for customization
dotnet aspnet-codegenerator identity -dc ApplicationDbContext --files "Account.Login;Account.Register;Account.Logout"
Blazor コンポーネントによるカスタム Identity UI
Blazor ネイティブの認証エクスペリエンスを実現するには、Identity API を呼び出す Blazor コンポーネントを作成します。
@page "/Account/Login"
@inject SignInManager<ApplicationUser> SignInManager
@inject NavigationManager Navigation
<EditForm Model="loginModel" OnValidSubmit="HandleLogin" 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.";
}
}
}
注意点: SignInManager は Cookie を設定するために HttpContext を使用します。Interactive レンダリングモードでは、回線が確立された後に HttpContext を使用できません。ログイン/ログアウトページでは、Cookie 操作に必要な HttpContext にアクセスできるよう、Static SSR(@rendermode なし)を使用する必要があります。
ロールベースおよびポリシーベースの認可
ページレベルの認可
@page "/admin"
@attribute [Authorize(Roles = "Admin")]
<h1>Admin Panel</h1>
@page "/products/manage"
@attribute [Authorize(Policy = "ProductManager")]
<h1>Manage Products</h1>
ポリシーの定義
builder.Services.AddAuthorizationBuilder()
.AddPolicy("ProductManager", policy =>
policy.RequireRole("Admin", "ProductManager"))
.AddPolicy("CanDeleteOrders", policy =>
policy.RequireClaim("permission", "orders.delete")
.RequireAuthenticatedUser())
.AddPolicy("MinimumAge", policy =>
policy.AddRequirements(new MinimumAgeRequirement(18)));
カスタム認可ハンドラー
public sealed class MinimumAgeRequirement(int minimumAge) : IAuthorizationRequirement
{
public int MinimumAge { get; } = minimumAge;
}
public sealed class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst("date_of_birth");
if (dateOfBirthClaim is not null
&& DateOnly.TryParse(dateOfBirthClaim.Value, out var dob))
{
var age = DateOnly.FromDateTime(DateTime.UtcNow).Year - dob.Year;
if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
// Register
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
コンポーネントでの手続き型認可
@inject IAuthorizationService AuthorizationService
@code {
[CascadingParameter]
private Task<AuthenticationState>? AuthState { get; set; }
private bool canEdit;
protected override async Task OnInitializedAsync()
{
if (AuthState is not null)
{
var state = await AuthState;
var result = await AuthorizationService.AuthorizeAsync(
state.User, "CanEditProducts");
canEdit = result.Succeeded;
}
}
}
外部 ID プロバイダー
外部プロバイダーの追加
builder.Services.AddAuthentication()
.AddMicrosoftAccount(options =>
{
options.ClientId = builder.Configuration["Auth:Microsoft:ClientId"]!;
options.ClientSecret = builder.Configuration["Auth:Microsoft:ClientSecret"]!;
})
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Auth:Google:ClientId"]!;
options.ClientSecret = builder.Configuration["Auth:Google:ClientSecret"]!;
});
ホスティングモデルごとの外部ログインフロー
| ホスティングモデル | フロー | 備考 |
|---|---|---|
| InteractiveServer / Static SSR | 標準的な OAuth リダイレクト(サーバー側) | コールバック後に Cookie を保存 |
| InteractiveWebAssembly | PKCE を使用した OIDC(クライアント側) | ブラウザーにトークンを保存 |
| ハイブリッド(MAUI) | WebAuthenticator または MSAL | プラットフォーム固有のセキュアストレージ |
WASM では、クライアントプロジェクトで OIDC プロバイダーを構成します。
// Client Program.cs
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = "https://login.microsoftonline.com/{tenant}";
options.ProviderOptions.ClientId = "{client-id}";
options.ProviderOptions.ResponseType = "code";
});
MAUI ハイブリッドの場合:
var result = await WebAuthenticator.Default.AuthenticateAsync(
new Uri("https://login.example.com/authorize"),
new Uri("myapp://callback"));
var token = result.AccessToken;
エージェント利用時の注意点
- インタラクティブコンポーネントで
HttpContextにアクセスしないでください。HttpContextを使用できるのは、最初の HTTP リクエスト中のみです。SignalR 回線が確立された後(InteractiveServer)、または WASM ランタイムが読み込まれた後は、nullになります。代わりにAuthenticationStateProviderまたはCascadingAuthenticationStateを使用してください。 - WASM でクロスオリジン API アクセスや委任された API アクセスに Cookie を使用しないでください。 クロスオリジン API には、
AuthorizationMessageHandlerと OIDC/JWT を使用してください。同一オリジンおよび Backend-for-Frontend(BFF)の Cookie 認証は、引き続き WASM アプリで使用できます。 - ログイン/ログアウトページを Interactive モードでレンダリングしないでください。
SignInManagerは Cookie の設定や削除にHttpContextを必要とします。ログインページとログアウトページでは、Static SSR レンダリングモードを使用する必要があります。 - XSS のリスクを考慮せずに
localStorageにトークンを保存しないでください。 アプリに XSS の脆弱性があると、localStorage内のトークンが盗まれる可能性があります。sessionStorage(タブを閉じると消去されます)、または PKCE と組み合わせた OIDC ライブラリの組み込みストレージ機構を使用してください。 AddCascadingAuthenticationState()を忘れないでください。 これがないと、コンポーネントの[CascadingParameter] Task<AuthenticationState>は常にnullとなり、認証チェックが気付かれないまま機能しなくなります。AddIdentityとAddDefaultIdentityを同時に使用しないでください。AddDefaultIdentityには UI のスキャフォールディングが含まれますが、AddIdentityには含まれません。既定の Identity UI ページを使用するかどうかに応じて、どちらかを選択してください。
前提条件
- .NET 8.0 以降(レンダリングモードを備えた Blazor Web アプリ、および
AddCascadingAuthenticationStateサービスの登録) - EF Core と Identity を使用するための
Microsoft.AspNetCore.Identity.EntityFrameworkCore - 既定の Identity UI のスキャフォールディング用の
Microsoft.AspNetCore.Identity.UI - 外部プロバイダー用の
Microsoft.AspNetCore.Authentication.MicrosoftAccount/.Google - Microsoft Identity(Azure AD/Entra)を使用する WASM 用の
Microsoft.Authentication.WebAssembly.Msal