ASP.NET Core Authentication
Table of Contents
はじめに
ASP.NET Core の認証について基本的な内容をまとめました。
用語の整理
ASP.NET Core に限った話ではありませんが、認証まわりを実装するにあたっては 認証 と 承認(認可) は別の概念であることを押さえておく必要があります。これらをひとくくりにして「認証」と呼ぶこともありますが、厳密には異なる事柄を指すため、区別できないと混乱が生じます。ぜひ覚えておきましょう。
- 認証(Authentication): 誰であるかを確認するプロセス
- 承認(Authorization): 認証されたユーザーに「何をする権限があるか」を決定するプロセス
認証は、パスワードや PIN コードを使った認証、生体認証、ワンタイムパスワードなど様々な方法がありますが「主張する本人であるか」を確認するプロセスになります。
承認は、「認証したユーザーに何が出来るのか」を決定するプロセスです。例えば管理者権限を持っているユーザーであればデータを更新できるが、一般ユーザーは閲覧のみ可能、などです。 承認を行うためにはまずユーザーが誰であるかを特定する必要がありますので、認証 -> 承認という順序になります。
ちなみに Authorization という語に対して マイクロソフトの日本語ドキュメントでは、承認 と 認可 という二つの訳語が使用されており読んでいて混乱しますが、英語の原文を見るとどちらも Authorization です。これは機械翻訳のせいだと思うので大目に見ましょう。このメモ書きでは Authentication=認証、Authorization=承認 という語で統一します。
ASP.NET Core Identity
ASP.NET Core で認証といえば ASP.NET Core Identity です。
ASP.NET Core Identity は多機能なフレームワークで、ユーザー認証用のテーブルからログイン画面やパスワード変更、二要素認証など、多くの機能を自動生成してくれます。ただし ASP.NET Core Identity は「フレームワーク的」な設計で、開発者がその流儀に従う必要があります。柔軟なカスタマイズが可能になっていますが、適切なカスタマイズを行うには ASP.NET Core Identity の作法を理解する必要がありますので、そういう意味では「重い」ライブラリです。
このメモ書きでは、ASP.NET Core Identity は直接扱わず、その基盤となっている ASP.NET Core の機能を使用して軽量に認証と承認を実装する方法をとりあげます。
ASP.NET Core の認証(Authentication)
認証の追加
ASP.NET Core に認証を追加するコードは以下のようになります。
// Program.cs
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Login";
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapStaticAssets();
app.MapRazorPages()
.WithStaticAssets();
app.Run();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
認証サービスを登録します。第1引数にはデフォルトの認証スキーム名を指定します。
CookieAuthenticationDefaults.AuthenticationSchemeは string 型の定数で、値は "Cookie" です。ASP.NET Core では複数の認証方法を同時に使用することができ、それぞれにスキーム名を割り当てることができます。AddCookie()
認証サービスが使用する認証方法に Cookie を追加します。
この例では options だけを指定していますが、スキーム名と options を受け取るオーバーロードがあり、割り当てるスキーム名を指定することができます。スキーム名を指定しなかった場合のデフォルト値は "Cookie" になります。つまり、このコードでは "Cookie" というスキーム名で Cookie 認証を追加し、それをデフォルトの認証方式として採用しています。
options.LoginPathは未認証のユーザーがアクセスしたときにリダイレクトされるログインページのパスになります。app.UseAuthentication()
ミドルウェアパイプラインに認証処理を追加します。
ログイン画面の追加
次に認証を行うログイン画面を作成します。
ユーザー名とパスワード、ログインボタンだけのシンプルな画面とします。
@* Login.cshtml *@
@page
@model LoginModel
<form method="post">
<div>
<label>ユーザー名:</label>
<input asp-for="Username" />
</div>
<div>
<label>パスワード:</label>
<input asp-for="Password" type="password" />
</div>
<button type="submit">ログイン</button>
<p style="color:red">@Model.ErrorMessage</p>
</form>
// Login.cshtml.cs
public class LoginModel : PageModel
{
[BindProperty]
public string Username { get; set; } = "";
[BindProperty]
public string Password { get; set; } = "";
public string ErrorMessage { get; set; } = "";
public async Task<IActionResult> OnPostAsync()
{
if (Username != "hoge" || Password != "fuga")
{
ErrorMessage = "ユーザー名またはパスワードが正しくありません";
return Page();
}
var claims = new List<Claim>
{
new(ClaimTypes.Name, Username)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return RedirectToPage("/Index");
}
}
ASP.NET Core の承認(Authorization)
次は承認です。
認証済みのユーザーのみページにアクセスできるようにするには [Authorize] 属性を設定します。
トップページ(Index)に追加してみましょう。
// Index.cshtml.cs
[Authorize]
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
トップページにアクセスするとこのように表示されます(図1)。
URL を見ると https://localhost:7092/Login になっています。
トップページにアクセス > 未認証のためログインページにリダイレクト > ログインページが存在しない
という流れになっています。
ログインページを作成しましょう。
ログインページの作成
全てのアクションに認証を要求する
未認証(未ログイン)のユーザーはどのページにもアクセスできないように設定してみます。
// Program.cs
builder.Services.AddAuthentication()
.AddCookie(options =>
{
// ログイン画面のURL
options.LoginPath = "/Login";
});
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy(new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build());
// ... 他の設定 ...
var app = builder.Build();
// ... ミドルウェアの設定 ...
app.UseAuthentication();
app.UseAuthorization();
これは全てのコントローラー、Razor Pages に [Authorize] 属性を設定することと等価。
このままだとログイン画面にもアクセスできなくなるので、ログイン画面に [AllowAnonymous] を設定することで、未ログイン(未認証)状態でもログイン画面が表示されるようになる。
[AllowAnonymous]
public class LoginModel : PageModel
ロール ベースの承認
[Authorize(Roles = "Administrator")]
public class AdministrationController : Controller
{
public IActionResult Index() => Content("Administrator");
}
- OR条件での承認
カンマで区切るとOR条件となる。
[Authorize(Roles = "HRManager,Finance")]
public class SalaryController : Controller
{
public IActionResult Payslip() =>
Content("HRManager || Finance");
}
- AND条件での承認
縦に並べるとAND条件となる。
[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]
public class ControlPanelController : Controller
{
public IActionResult Index() =>
Content("PowerUser && ControlPanelUser");
}
ポリシーベース、クレームベースの承認も同様だが
- コントローラーの場合:コントローラー単位、アクション単位での承認が可能
- Razor Pages の場合:Razor Pages 単位のみの承認が可能( アクション(ハンドラー)単位での承認は設定できない ) という制限があるため、Razor Pages で更新権限/参照権限のような切り分けを考える場合「更新機能は別ページとする」などの考慮が必要となる。
参考資料
ASP.NET Core の認証の概要
ASP.NET Core での認可の概要
ASP.NET Core Identity なしで Cookie 認証を使用する
