ASP.NET Core の RazorPage で エリアやページごとにアクセス制限をかける

# はじめに

業務で ASP.Net Core MVC を使っていたのですが、RazorPage でウェブアプリを作ったことはなかったので、勉強かねて仮想的なECサイトを作っていました。

その中で、いくつかハマった事をまとめておこうと思います。
今回は、アクセス制限のかけ方についてまとめます。

# 想定している状況

デジタル作品をアップロードして、販売できるECサイトを想定して解説します。

## フォルダおよびURLの構成

以下は、ソリューション内のフォルダ構成で、カッコ内はURLです。
解説に必要な部分だけ示しています。

なお、フォルダ構成と URL は一致しているわけではありません。
例えば、カートページは Purchase フォルダ内のページですが、 /Cart でアクセスできるような状況を想定しています。

```
/
├── Areas/
│    └── Seller/
│        └── Pages/
│            ├── Items/
│            │   └── Manage/
│            │       ├── Index.cshtml（/Seller/Items/Manage）
│            │       ├── ...
│            │
│            └── Register.cshtml（/Seller/Items/Register）
├── Pages/
│    ├── Purchase/
│    │   ├── Cart.cshtml（/Cart）
│    │   ├── Payment.cshtml（/Purchase/Payment）
│    │   ├── Confirm.cshtml（/Purchase/Confirm）
│    │   ├── ...
│    │
│    └── User
│        ├── Purchase.cshtml（/User/Purchase）
│        ├── Profile.cshtml（/User/:Uid/Profile）
│        ├── ...
│
└── StartUp.cs
```

# アクセス制限の設定方法

ここでは、StartUp.cs 内で設定する方法を解説します。
以下のコードは、すべて `ConfigureService` 内に記述します。

## 販売者関連ページ（/Areas/Seller/）

Seller エリアにあるすべてのページは、ログインしていて、なおかつロールに"Seller"が設定されているユーザーのみアクセスできるように設定します。

事前に、ポリシーを追加しておきます。

```csharp
services.AddAuthorization(
{
  // アクセス制限用ポリシーを追加
  options.AddPolicy("SellerPolicy", policy => policy.RequireRole("Seller"));
});
```

このアクセスポリシーを、Seller エリアに適用します。
エリアに承認規則を設定するには、`AuthorizeAreaFolder` を使います。
フォルダーパスは、エリアフォルダを基準にした相対パスを設定します。

```csharp
AuthorizeAreaFolder("エリア名", "フォルダーパス", "ポリシー")
```

実際のコードは以下のようになります。

```csharp
services.AddMvc().AddRazorPagesOptions(options =>
{
  // 販売者の作品関連ページ
  options.Conventions.AuthorizeAreaFolder("Seller", "/Items", "SellerPolicy");
});
```

## 購入関連ページ（/Pages/Purchase/）

以下のような承認規則を設定します。

『購入関連のページは、ログインユーザー以外アクセスできないようにしたいが、カートページだけはログインしていないユーザーもアクセスできるようにする』

この場合、**規則を設定する順番**が重要になります。
まず始めに、Purchase フォルダーに対してログインユーザー以外アクセスできないように設定します。
その後、カートページに対して、`AllowAnonymousToPage` を使って、だれでもアクセスできるように設定をしすます。

```csharp
AllowAnonymousToPage("ページ（.cshtml）パス")
```

実際のコードは以下のようになります。

```csharp
services.AddMvc().AddRazorPagesOptions(options =>
{
  // 購入関連ページ
  options.Conventions.AuthorizeFolder("/Purchase");
  // カートページはログインしていなくても許可
  options.Conventions.AllowAnonymousToPage("/Purchase/Cart");
  
  // NOTE: まとめて以下のように書いても良い
  // options.Conventions
  //  .AuthorizeFolder("/Purchase")
  //  .AllowAnonymousToPage("/Purchase/Cart");
});
```

Authorize~ よりも AllowAnonymous~ の方が強いので、先に `.AllowAnonymousToFolder("/Purchase")` とした後、`.AuthorizePage("/Purchase/カード以外のページ") ` とやっても Purchase 以下のページはだれでもアクセスできてしまいます。

## 登録ユーザー関連ページ（/User/）

ユーザー関連ページの購入作品一覧ページのみ、ログインしているユーザー本人がアクセスできるように設定します。

特定のページにのみ承認規則を設定するには、`AuthorizePage` を使います。

```csharp
AuthorizePage("ページパス")
```

実際のコードは以下のようになります。

```csharp
services.AddMvc().AddRazorPagesOptions(options =>
{
  // 購入作品一覧ページ
  options.Conventions.AuthorizePage("/User/Purchase");
});
```

## コード全体

```csharp
services.AddAuthorization(
{
  // アクセス制限用ポリシーを追加
  options.AddPolicy("SellerPolicy", policy => policy.RequireRole("Seller"));
});

services.AddMvc().AddRazorPagesOptions(options =>
{
  // 販売者の作品関連ページ
  options.Conventions.AuthorizeAreaFolder("Seller", "/Items","SellerPolicy");
  // 購入関連ページ
  options.Conventions.AuthorizeFolder("/Purchase");
  // カートページはログインしていなくても許可
  options.Conventions.AllowAnonymousToPage("/Purchase/Cart");
  // 購入作品一覧ページ
  options.Conventions.AuthorizePage("/User/Purchase");
});
```

# 参考

他にもアクセス制限のかけ方はあります。詳しく知りたい方は、公式ページを参照してください。

[公式 - Razor Pages authorization conventions in ASP.NET Core](https://docs.microsoft.com/ja-jp/aspnet/core/security/authorization/razor-pages-authorization?view=aspnetcore-2.2#note-on-combining-authorized-and-anonymous-access)

未完成ですが、デジタルデータを販売できるECサイトを想定したプロジェクトを用意したので、該当するファイルへのリンクも貼っておきます。

[GitHub - EStalls StartUp.cs](https://github.com/tatsuteb/EStalls/blob/develop/EStalls/Startup.cs)