プロジェクト作成
dotnet new blazor -n BlazorApp1
Nuget追加
cd BlazorApp1
dotnet add package Microsoft.AspNetCore.Components.Authorization
CustomAuthStateProvider追加
AuthenticationStateProviderを継承し、ログイン情報を取得・保存処理を行う。
ユーザ情報のUserSessionはjsonにし、保存する形
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.JSInterop;
namespace BlazorApp1
{
public record UserSession(string UserId, string UserName, string Role);
public class CustomAuthStateProvider : AuthenticationStateProvider
{
private ClaimsPrincipal _anonymous = new(new ClaimsIdentity());
private ClaimsPrincipal _currentUser;
private IJSRuntime _jsRuntime;
private const string StrorageKey = "authToken";
public CustomAuthStateProvider(IJSRuntime jSRuntime)
{
_jsRuntime = jSRuntime;
_currentUser = _anonymous;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
var json = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", StrorageKey);
if (!string.IsNullOrEmpty(json))
{
var session = JsonSerializer.Deserialize<UserSession>(json);
if (session != null)
{
var identity = new ClaimsIdentity(
[
new Claim(ClaimTypes.Name, session.UserId),
new Claim(ClaimTypes.Role, session.Role)
], "Custom Authentication");
_currentUser = new ClaimsPrincipal(identity);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in GetAuthenticationStateAsync: {ex.Message}");
_currentUser = _anonymous;
}
return await Task.FromResult(new AuthenticationState(_currentUser));
}
public async Task MakeAuthenticateUser(string userIdentifier)
{
var session = new UserSession(userIdentifier, userIdentifier, "User");
var json = JsonSerializer.Serialize(session);
var identity = new ClaimsIdentity(
[
new Claim(ClaimTypes.Name, session.UserId),
new Claim(ClaimTypes.Role, session.Role)
], "Custom Authentication");
_currentUser = new ClaimsPrincipal(identity);
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", StrorageKey, json);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_currentUser)));
}
public async Task MakeUserAsLoggedOut()
{
_currentUser = _anonymous;
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", StrorageKey);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_currentUser)));
}
}
}
- UserSession : ログインユーザー情報のオブジェクト
- GetAuthenticationStateAsync : 画面ロード時に呼び出され現在ユーザー情報をセットする。
- MakeAuthenticateUser : ログイン処理
- MakeUserAsLoggedOut : ログアウト処理
Programs.cs修正
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components.Authorization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/signin";
});
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
builder.Services.AddScoped<CustomAuthStateProvider>(sp => (CustomAuthStateProvider)sp.GetRequiredService<AuthenticationStateProvider>());
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
_Imports.razorにusing追加
@using Microsoft.AspNetCore.Components.Authorization
App.razor修正
rendermodeを統一しておかないと、認証情報取得時不一致になる。
<head>
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
</body>
Routes.razor修正
認証状態の場合のみ表示するようにする。
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="routeData" DefaultLayout="@typeof(Layout.MainLayout)">
<NotAuthorized>
<p role="alert">このページを表示する権限がありません。</p>
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(Layout.MainLayout)">
<p role="alert">ページが見つかりません。</p>
</LayoutView>
</NotFound>
</Router>
各Pageに認証設定する。
これにより、Programs.csの「options.LoginPath = "/signin";」が動作し、初期ページへ移動する。
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@attribute [Authorize(Roles = "Admin,User")]
SignIn.razor追加
@page "/signin"
@rendermode InteractiveServer
@inject AuthenticationStateProvider AuthenticationStateProvider
<input @bind="userIdentifier" />
<button @onclick="OnSignIn">Sign in</button>
<button @onclick="OnSignOut">Sign out</button>
<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized.</p>
</NotAuthorized>
</AuthorizeView>
@code {
public string userIdentifier = string.Empty;
private async Task OnSignIn() => await ((CustomAuthStateProvider)AuthenticationStateProvider).MakeAuthenticateUser(userIdentifier);
private async Task OnSignOut() => await ((CustomAuthStateProvider)AuthenticationStateProvider).MakeUserAsLoggedOut();
}
とりあえずここまで
GitHub - haronoid/BlazorApp1 · GitHub
今後の課題
- ブラウザを閉じた時にログアウトはどうする?
- Tokenで認証に有効期間設定