やること
ASP.NET Core MVC の MVC アーキテクチャに Identity を組み込んで認証機能を追加します。
Visual Studio で提供される MVC + Identity のテンプレートでは MVC に Razor Pages を追加する形になっていて気持ち悪い...全部 MVC に組み込みたい!ということでこのようなことをやります。
開発環境は Visual Studio for Mac です。
作るもの
認証機能付きの簡単な ToDo アプリを作ってみます。
仕様は以下のようにします。
- アカウントは名前とパスワードのみで登録
- 各アカウントが登録するタスクは独立している
- つまり、A さんが登録するタスクは B さんから見ることができない。逆も然り。
- CRD だけ実装して、U は実装しない(別にしてもいいけど、省略)
やっていくぞ
1. 新規プロジェクトを作成
web アプリケーション (MVC), .NET Core3.1, 認証なし, と選択していきます。
2. NuGet パッケージ
必要になるものをあらかじめインストールしてしまいましょう。
以下の 6 つをインストールします。.NET Core 3.1 でやってるので、各パッケージのバージョンも 3.1.x で設定しておきます。
- AspNetCore.Identity
- AspNetCore.Identity.EntityFramework
- AspNetCore.Identity.UI
- EntityFrameworkCore
- EntityFrameworkCore.Design
- EntityFrameworkCore.Sqlite
3. データベースコンテキストの作成
各ユーザーの認証情報はデータベースに格納されるので、まずはデータベースを使えるようにするところから。
普通にデータベース作るだけなら、DbContext
を継承したクラスを作ればいいのですが、Identity で使うデータベースコンテキストは IdentityDbContext
を継承します。このクラスには Users
とか、認証情報を格納する DbSet
がすでにプロパティに入っているので、継承さえしてしまえば OK です。
namespace ToDoIdentity.Data
{
public class ToDoContext : IdentityDbContext
{
public ToDoContext(DbContextOptions<ToDoContext> options)
: base(options) { }
}
}
4. Identity を使うために Startup.cs を編集
ここがややこしい...ぶっちゃけおまじない状態
Startup.ConfigureServices
やることは 3 つ。
- データベースコンテキストを使えるようにする
- Identity を使えるようにする
- 認証機能の設定をする
ConfigureServices
に以下のようなコードを追加します。
services.AddDbContext<ToDoContext>(options =>
Configuration.GetConnectionString("DefaultConnection")));
// Identity を追加
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddEntityFrameworkStores<ToDoContext>();
// Identity を設定
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters = null; // UserName に使える文字の制限をなしにする
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Login";
options.AccessDeniedPath = "/Identity/AccessDenied"; //AccessDenied は未実装
options.SlidingExpiration = true;
});
services.AddDefaultIdentity<IdentityUser>
で、ユーザー情報として使うクラスを設定します。ここではデフォルトの IdentityUser
を使っていますが、IdentityUser
を継承したクラスでも設定できます(詳しくはこのへんを見ると良い)
Identity の設定はこれを見れば良い。
Startup.Configure
app.UseAuthentication()
を app.UseRouting()
と app.UseAuthorization()
の間に入れます。
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
これで、詳しいことはよくわからんが、HTTP リクエストが認証ミドルウェアを通るようになるらしい。
5. 認証機能の実装
さてここからバシバシとコードを書いていくわけですが、ここまでくれば普通の MVC の考え方を使っていけばいいです。
つまり、Identity の Model, View, Controller を作っていきます。
View
ログイン画面とアカウント登録画面を作って、それぞれにフォームを追加するだけ。
例えばログイン画面はこんな感じ
@model IdentityInputModel
@{
ViewData["Title"] = "Login";
}
<h1>ログイン</h1>
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="UserName" class="control-label"></label>
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input type="password" asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<input formmethod="post" type="submit" value="ログイン" class="btn btn-primary" />
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Model
View からフォームで post されたものを格納できれば良いです。
namespace ToDoIdentity.Models
{
public class IdentityInputModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
}
Controller
ここはちょっと普通の Controller と違う部分が出てきます。というのも、ここでログインしたりログアウトしたりという処理を行うからです。
まずはコンストラクタの引数で UserManager
と SignInManager
のインスタンスを受け取ります。これらはそれぞれ、リクエスト元のユーザー情報を取得するインスタンス、ログイン関係の処理を行うインスタンスです。
public IdentityController(UserManager<IdentityUser> _userManager,
SignInManager<IdentityUser> _signInManager)
{
userManager = _userManager;
signInManager = _signInManager;
}
これらを使って、ログイン、ログアウト、アカウント登録を行っていきましょう。
ログイン
var result = await signInManager.PasswordSignInAsync(model.UserName, model.Password, true, false);
これでログイン処理を行い、その結果が result
に格納されます。ログインの成功・失敗によってリダイレクト先を変えたりできます。
ログアウト
await signInManager.SignOutAsync();
これだけ!
アカウント登録
var user = new IdentityUser() { UserName = model.UserName };
var result = await userManager.CreateAsync(user, model.Password);
こっちは userManager
を使って行います。ユーザーインスタンスとパスワードを引数に入れて登録です。これも成功したかどうかが result
に入れられます。
6. 普通に ToDo アプリを作っていく
さて、これにて認証機能は実装できたので、あとは ToDo の CR(U)D を実装していくだけですね。
ところが、仕様をもう一度確認してみると
各アカウントが登録するタスクは独立している
とあります。
これは、ToDo モデルにユーザー ID を持たせることで実装できそうです。
namespace ToDoIdentity.Models
{
public class ToDoItem
{
[Key]
public int Id { get; set; }
public string ToDo { get; set; }
public string UserId { get; set; }
}
}
本当は UserId
を外部キーにした方がいいんですけど、ここでは省略。
さて、あとは Controller で ToDo モデルに UserId
を渡すようにすれば OK です。Controller ではユーザーの情報を以下のようにして取得することができます。
var newItem = new ToDoItem()
{
UserId = userManager.GetUserId(User),
ToDo = toDoName
};
このように userManager
からユーザーの情報を取得できます。UserManager
インスタンスは、IdentityController
でやったのと同様に、コンストラクタの引数で受け取れます。
完成!!
できた〜!
ということでコードをこちらにあげています。参考にしてみてください!
おわり
ぶっちゃけ Identity は高機能すぎて使いこなせてないです...ちゃんと勉強しなきゃー