Identityにroleを追加する
Asp.Net Core MVCでプロジェクトを作り、Identityを追加するとDBにいくつかTableが作られるけど、Identityの設定ではRole追加が無い(らしい?)
「じゃ、Table作るなよ。」って感じもするけど、Roleはどうやって設定するのだろうかといろいろ検索した結果。自分で追加するらしい。
という事で、追加してみました。
Role用のContoroller、Viewを作ります。
参考
Adding Role Authorization to a ASP.NET MVC Core Application
How to work with Roles in ASP.NET Core Identity
製作
・VisualStudio2022
・Asp.Net Core Mvc
RoleSampleでプロジェクトを作りました。
Identityを追加
add-migration InitialMigration
update-database
マイグレーションするとDBはこんな感じになります。Identityの標準仕様らしい。
_Layout.cshtmlにログイン等のメニューを追加
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">RoleSample</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
<partial name="_LoginPartial" />
</div>
</div>
</nav>
</header>
実行して表示確認。
Role用のControllerとViewを作ります。
RoleController
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;
namespace RoleSample.Controllers
{
public class RoleController : Controller
{
RoleManager roleManager;
public RoleController(RoleManager roleManager)
{
this.roleManager = roleManager;
}
public IActionResult Index()
{
var roles = roleManager.Roles.ToList();
return View(roles);
}
public IActionResult Create()
{
return View(new IdentityRole());
}
[HttpPost]
public async Task Create(IdentityRole role)
{
await roleManager.CreateAsync(role);
return RedirectToAction("Index");
}
}
}
ViewsにRoleフォルダを作りIndex.cshtmlとCreate.cshtmlを作ります。
Index.cshtml
@model IEnumerable<Microsoft.AspNetCore.Identity.IdentityRole>
@{
ViewData["Title"] = "Role Index";
}
<h1>List of Roles</h1>
<p>
<a asp-action="Create">Create</a>
</p>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
@foreach (var role in Model)
{
<tr>
<td>@Html.DisplayFor(modelItem => role.Id)</td>
<td>@Html.DisplayFor(modelItem => role.Name)</td>
</tr>
}
</tbody>
</table>
Create.cshtml
@model Microsoft.AspNetCore.Identity.IdentityRole
@{
ViewData["Title"] = "Role Create";
}
<h1>Create Role</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
_Layout.cshtmlにrole indexへのリンクを追加
Program.csを変更
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using RoleSample.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("RoleSampleContextConnection") ?? throw new InvalidOperationException("Connection string 'RoleSampleContextConnection' not found.");
builder.Services.AddDbContext(options =>
options.UseSqlServer(connectionString));
//builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true)
// .AddEntityFrameworkStores();
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddDefaultUI()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/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.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();;
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
実行すると
Roleの登録ができたら、identityのRegister.cshtmlとRegister.cs.html.csを変更します。
Register.cshtml.cs
namespace RoleSample.Areas.Identity.Pages.Account
{
public class RegisterModel : PageModel
{
private readonly SignInManager _signInManager;
private readonly UserManager _userManager;
private readonly IUserStore _userStore;
private readonly IUserEmailStore _emailStore;
private readonly ILogger _logger;
private readonly IEmailSender _emailSender;
private readonly RoleManager _roleManager;
public RegisterModel(
UserManager userManager,
IUserStore userStore,
SignInManager signInManager,
ILogger logger,
IEmailSender emailSender,
RoleManager roleManager)
{
_userManager = userManager;
_userStore = userStore;
_emailStore = GetEmailStore();
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
_roleManager = roleManager;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public IList ExternalLogins { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
// Role
public string Rolename { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
ViewData["roles"] = _roleManager.Roles.ToList();
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
var role = _roleManager.FindByIdAsync(Input.Rolename).Result;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
await _userManager.AddToRoleAsync(user, role.Name);
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by clicking here.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ViewData["roles"] = _roleManager.Roles.ToList();
// If we got this far, something failed, redisplay form
return Page();
}
Register.cshtml
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
var roles = (List<IdentityRole>)ViewData["roles"];
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h2>Create a new account.</h2>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-floating">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" />
<label asp-for="Input.Email"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" />
<label asp-for="Input.Password"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" />
<label asp-for="Input.ConfirmPassword"></label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<div class="form-floating">
<label asp-for="Input.Rolename"></label>
<select asp-for="Input.Rolename" class="form-control" asp-items="@(new SelectList(roles, "Id", "Name"))"></select>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h3>Use another service to register.</h3>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
DB Tableは、Users
UserRoles
Roles
この後はコントローラやアクションで「ロールベースの承認」を入れてロール認証はOKなはず。
ASP.NET Core でのロール ベースの認可 | Microsoft Learn
[Authorize(Roles = "Administrator")]
public class AdministrationController : Controller
{
public IActionResult Index() =>
Content("Administrator");
}