ASP.NET CoreでIDスキャフォールディング後にRoleを追加する話。

# 概要
ASP.NET Coreで、認証無しのテンプレートにID スキャフォールディングで追加した後に、IdentityRoleを追加するサンプルです。
IDスキャフォールディングで追加すると、UserのみでRoleは使わないテンプレートが作られます。

たとえば、

- "Administrator"グループのユーザにだけ制限したページを作りたい

ときにRoleを使うと便利なのですが、意外とここからどうすればいいかよくわかりません。

Razor Pageベースのモノが見当たらず、あっても今度はUserStoreやらRoleStoreやらをフルに実装するモノしかなく大変ややこしかったので一から作ってみました。

# 環境
- Visual Studio 2017 Professional( Build Toolsでもたぶん大丈夫)
- .NET Core 2.1
- ASP.NET Core 2.1
- Razor Pages

# ソース
完全なソースは、
https://github.com/imudak/IdRoleSample
にあります。

# 追加手順
以下の手順で変更を加えていきます。
リンクはgithubのcommitに対応しています。

[1. ASP.NET Core WEbアプリケーションの作成](https://github.com/imudak/IdRoleSample/commit/5aba151bcb283d4cc66721e10d834ebb4847096c)
[2. ID スキャフォールディング Login, Logout, AccessDenied, Registerを実装。](https://github.com/imudak/IdRoleSample/commit/e63af208389f76d341276231706c6f19e9fc84f9)
[3. NuGetパッケージ更新。](https://github.com/imudak/IdRoleSample/commit/8bf081ad77ec76da612eeabb0467bfe369945384)
[4. app.UseAuthentication()追加。](https://github.com/imudak/IdRoleSample/commit/7a82e9556f302464aa92fb7b4b50070cf0bbfe76)
[5. Loginメニュー追加。](https://github.com/imudak/IdRoleSample/commit/4af06199c3578a8cabe72ea59133e48d2ba30368)
[6. EmailをUserNameに変更。外部ログイン認証へのリンク削除。](https://github.com/imudak/IdRoleSample/commit/f7abb2021efc9ccdf185d48856ffdba38354bd29)
[7. 簡単のため、UseInMemoryDatabase()を使用。](https://github.com/imudak/IdRoleSample/commit/b0d829f44f19dd4d0f6bc21337c4c1261d2660ac)
[8. Contactを認証が必要なページとする。](https://github.com/imudak/IdRoleSample/commit/3e866735695461adea8eb651b1d7a8562402027e)
[9. IdentityRoleを追加。](https://github.com/imudak/IdRoleSample/commit/79e55c3d62fc62934804716988b5557e8a3decc3)
[10. 起動時にAdministrator Roleを生成。](https://github.com/imudak/IdRoleSample/commit/67df313378e70ecfc8bedc48979ad680e22a5c1c)
[11. Register時にRoleの項目(IsAdministrator)追加。](https://github.com/imudak/IdRoleSample/commit/a0c73d910146f0cfcd12fb25b0a01fc15c088142)
[12. Policy = "RequireAdministratorRole"追加。 ](https://github.com/imudak/IdRoleSample/commit/b2b4958b35cb8420c20c63ee5e7d28573df331fe)


詳細は、リンク先の[github](https://github.com/imudak/IdRoleSample)　を見ていただくとして:sweat_smile:肝心なとこだけ書きます。

## 9. IdentityRoleを追加。

### IdentityDbContextの書き換え
/Areas/Identity/Data/IdRoleSampleContext.cs の親クラスを変更します。

```C#
 namespace IdRoleSample.Models
 {
-    public class IdRoleSampleContext : IdentityDbContext<IdentityUser>
+    public class IdRoleSampleContext : IdentityDbContext<IdentityUser, IdentityRole, string>
     {
         public IdRoleSampleContext(DbContextOptions<IdRoleSampleContext> options)
             : base(options)
```

### AddDefaultIdentityをAddIdentityに変更
/Areas/Identity/IdentityHostingStartup.csにて、

- AddDefaultIdentityからAddIdentityにして、IdentityRoleを追加
- AddDefaultUI()を追加。

```C#
namespace IdRoleSample.Areas.Identity
{
    public class IdentityHostingStartup : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((context, services) =>
            {
                services.AddDbContext<IdRoleSampleContext>(options =>
                    options.UseInMemoryDatabase("IdRoleSampleContext"));
                //options.UseSqlServer(
                //    context.Configuration.GetConnectionString("IdRoleSampleContextConnection")));

-                services.AddDefaultIdentity<IdentityUser>()
-                    .AddEntityFrameworkStores<IdRoleSampleContext>();
+                services.AddIdentity<IdentityUser, IdentityRole>()
+                    .AddEntityFrameworkStores<IdRoleSampleContext>()
+                    .AddDefaultUI();
            });
        }
    }
}
```

AddDefaultUI()追加ですが、これしないと認証失敗したときになぜか/Identity/Account/Login/に飛んでくれませんでした。

## 12. Policy = "RequireAdministratorRole"追加。
/Areas/Identity/IdentityHostingStartup.csにて"RequireAdministratorRole"というPolicyを追加しています。

```C#
    public class IdentityHostingStartup : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((context, services) =>
            {
                services.AddDbContext<IdRoleSampleContext>(options =>
                    options.UseInMemoryDatabase("IdRoleSampleContext"));
                //options.UseSqlServer(
                //    context.Configuration.GetConnectionString("IdRoleSampleContextConnection")));

                services.AddIdentity<IdentityUser, IdentityRole>()
                    .AddEntityFrameworkStores<IdRoleSampleContext>()
                    .AddDefaultUI();
+
+                services.AddAuthorization(options =>
+                {
+                    options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
+                });
            });
        }
    }
```

この"RequireAdministratorRole"ポリシーを使って、/Contact/ページには"Administrator"Roleのユーザーでないとアクセスできないようにしています。

```C#
namespace IdRoleSample.Pages
{
    [Authorize(Policy = "RequireAdministratorRole")]
    public class ContactModel : PageModel
    {
        public string Message { get; set; }

        public void OnGet()
        {
            Message = "Your contact page.";
        }
    }
}
```

# 参考

- [ASP.NET Core プロジェクトでスキャフォールディング Id | Microsoft Docs](https://docs.microsoft.com/ja-jp/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-2.1&tabs=visual-studio)
- [Hishamのブログ - Razor Pagesでのロールベースの認証](http://hishambinateya.com/role-based-authorization-in-razor-pages)
- [Razor pages assign roles to users - Stack Overflow](https://stackoverflow.com/questions/51483737/razor-pages-assign-roles-to-users)