ASP.NET Core Blazor Server でオレオレ ログインを作りたい
社内とかで使うようなアプリとかを、サクッと作るのに ASP.NET Core Blazor Server を使うときに無防備に公開はしたくない!でも ASP.NET Core Identity とかを使うのはめんどくさい…みたいなジレンマに襲われることがあると思います。Azure AD などの外部の認証プロバイダーもそうそう使える状態にないという場合だと、相変わらずアプリ内で認証が必要になると思います。そもそもユーザーの情報は独自の DB に入ってるんだよ…というケースですね。
ASP.NET Core Blazor Server のデフォルトの認証情報は HttpContext.User
からとって来るようなので HttpContext.SignInAsync
で Cookie に認証情報設定してやれば、あとはよしなにやってくれるようになっています。ただ、ログインページとかは ASP.NET Core MVC とか Razor Pages で普通に作ってログイン成功後に Blazor Server のページに行く感じがやりやすそうです。実際に ASP.NET Core Blazor Server のプロジェクトテンプレートで「個別のアカウント」を認証方式に設定した奴でも Razor Pages でログアウトページとかが作られていますし、ログイン処理自体は ASP.NET Core Identity が提供するページのほうでやってます。
オレオレ ログイン処理にしたい
これを自前のログイン処理に置き換えてみましょう。新規作成で認証の種類を「個別のアカウント」にして作成します。Areas フォルダーの下にある Identity フォルダーをさくっと消しましょう。
そして、Program.cs を以下のようにします。
using BlazorApp2.Data;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
var builder = WebApplication.CreateBuilder(args);
// 認証を Cookie での認証にする
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>(); デフォルトの AuthenticationStateProvider を使うのでコメントアウト
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages(); // Razor pages 有効化
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Cookie ベースの認証は、公式ドキュメントの以下のページにまとまってるので、そこを参考に Program.cs を変更しました。
独自のログイン画面を以下のような感じで用意します。
そして以下のように実装しましょう。今回はお試しなので名前さえ入力したら素通りでログインできるものにしていますが、ここは各自の要件に応じて実装しましょう。
@page
@using Microsoft.AspNetCore.Authentication
@using System.Security.Claims
@model BlazorApp2.Areas.MyLogin.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
Name: <input asp-for="SignInUser.Name" />
<input type="submit" />
</form>
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
namespace BlazorApp2.Areas.MyLogin.Pages;
[IgnoreAntiforgeryToken]
public class IndexModel : PageModel
{
[BindProperty]
public User? SignInUser { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid is false)
{
return Page();
}
// 認証して、Blazorのほうにリダイレクト
var principal = new ClaimsPrincipal(new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, SignInUser!.Name!),
new Claim(ClaimTypes.Role, "Administrator"),
}, CookieAuthenticationDefaults.AuthenticationScheme));
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal);
return Redirect("~/");
}
public void OnGet()
{
}
}
public class User
{
[Required]
public string? Name { get; set; }
}
後は、もとからある LoginDisplay.razor のログインページへのリンクを自作のログインページの URL に置き換えます。
<AuthorizeView>
<Authorized>
<a href="Identity/Account/Manage">Hello, @context.User.Identity?.Name!</a>
<form method="post" action="Identity/Account/LogOut">
<button type="submit" class="nav-link btn btn-link">Log out</button>
</form>
</Authorized>
<NotAuthorized>
<a href="MyLogin/Index">Log in</a>
</NotAuthorized>
</AuthorizeView>
実行してみましょう。
起動直後はログインしていないので Log in リンクが表示されます。
Log in リンクを選択すると自作のログイン画面に飛びます。
名前を入れてボタンを押します。
無事ログイン状態になって、画面に名前が表示されました。
いい感じ。
まとめ
Azure AD とか使えたらそっちを使いましょう。
そうじゃないケースでは ASP.NET Core Identity を使ったほうが認証系のベストプラクティス的なものが色々実装されているので便利です。
それがどうしても合致しない場合は、今回紹介したような方法も使えますね。
一応、ソースは以下のリポジトリに上げておきます。
余談ですが AuthenticationStateProvider
を継承してダミーの認証情報を返すようなものを作って開発中は DI コンテナにそっちを設定するようにしておくと、ログイン状態でアプリを動かすのに便利です。
Discussion