ASP.NET Identity メモ


ASP.NET Identityを利用する際の基本的なメモ。
私のバカな頭では全体像がつかめなかったので、とりあえず簡単なユーザー登録、認証、Role等をテストしてみます。

>なお、以下のコードは可動性重視？のためawait等は使わず同期処理で記述しています。
また、ASP.NET MVC 5を前提としています（Coreはまた別の機会に）。

##認証用に利用するDBを作成する

まず、ASP.NET Identityを利用した認証がとっつきにくいのはlocaldbに対して勝手にcode firstでテーブルが作られてしまうとこかなと。これはWeb.configのDefaultConnectionで設定されています。


###独自DBに変える

本番だと普通はSQL Server使うと思うので変更します。変え方は、

* Web.configのDefaultConnectionを変える
* 新しいConnectionStringを定義する

の2つかと思います。

Web.configを書き換える場合は、例えば下記の様に変更します。

>各パラメータは環境に合わせて適宜読み替えお願いします。

```xml
<add name="DefaultConnection"
    connectionString="Data Source=localhost;Initial Catalog=testdb;User ID=sa;Password=password"
    providerName="System.Data.SqlClient"/>
```

新しく接続文字列を定義した場合はModels/IdentityModels.csのApplicationDbContext内のbase:を変更します。


```csharp
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}
```

###テーブルを生成する

Code Firstで実行時に生成されるのはデモとしてはいいのですけど、実務では開発者の意図するタイミングで生成しておきたいこともあります。その場合は、Migrationしてやればいいようです。

ツール -> NuGetパッケージマネージャー -> パッケージマネジャーコンソールを開き、

```bash
Enable-Migrations
Add-Migration migration_filename
Update-Database
```

で生成されます。

>migration_filenameのところは適宜書き換えて下さい。

> 上記実行後、変化が発生した際は、再度、Add-Migration, Update-Databaseを実行します。

生成されるデータベースは

* AspNetRoles
* AspNetUserClaims
* AspNETUserLogins
* AspNetUserRoles
* AspNetUsers

です。最低限必要なテーブルはAspNetUsersです。

##DBにUserを登録する

自動生成される登録画面を使う方法がよく紹介されていますが、汎用性が無いためコードで登録してみます。
なお、標準の認証機構を利用する場合、最低限、

* UserName（自動生成コードではEmailが挿入されるようになっているみたい）
* Email
* SecurityStamp

の設定が必要となるようです。

>SecurityStampはApp_Start/Auth.csのapp.UseCookieAuthentication()にて検証させるように設定されているようです。

なお、ここでは標準で用意されているHomeControllerに、

* ユーザーの追加 (createuser)
* ログイン (login)
* ログオフ (logoff)

上記の3つのActionを実装してみたいと思います。

###ModelsとIdentiryをusing

必要な名前空間をusingします。

###using

必要な名前空間をusingします。Modelsは環境に合わせて変更して下さい。

```csharp
using System;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using APP_NAME.Models;　//環境依存
```

###CreateUser

普通にEntityFrameworkのINSERTと同じです。が、認証時のパスワードの突き合わせはハッシュ化されたパスワードを前提として行われるのでPasswordはHash化しておきます。またSecurityStampが無いと認証時に「オブジェクトがnullです！」的なエラーが出るのでとりあえず、Guidでも書き込んでおきます。

```csharp
public ActionResult CreateUser()
{
    //ユーザーの追加
    var user = new ApplicationUser();

    user.UserName = "test@test.com";
    user.Email = "test@test.com";
    //ハッシュ化する
    user.PasswordHash = new PasswordHasher().HashPassword("test@test.com");
    //SecurityStampを設定する（これがNullだと認証でエラーとなる）
    user.SecurityStamp = Guid.NewGuid().ToString();

    //追加処理
    ApplicationDbContext adb = new ApplicationDbContext();
    adb.Users.Add(user);
    adb.SaveChanges();

    //そのままサインイン処理
    var signInManager = this.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
    signInManager.SignIn(user, false, false);

    return RedirectToAction("Index", "Home");
}
```

ユーザーの生成と同時にログインさせていますが、必須ではありません。

なお、生成の部分は、userManagerのCreateを利用することもできます（自動生成のregister actionは下記の様になっています）。

```csharp
//抜粋
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
```

>なお、ApplicationUserManager, ApplicationSignInManager, ApplicationRoleManagerはApp_Start/IdentityConfig.cs内で定義されている。

##ログイン

ユーザーが登録できたらログインしてみます。
userManagerにより対象にユーザーの存在をチェックし、ユーザーがいたなら引き続き認証を行います。

```csharp
public ActionResult Login()
{
    //UserManager生成（ユーザーを検索するため：生成にも使える）
    var userManager = this.HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    var user = userManager.Find("test@test.com", "test@test.com");

    //SignInManager生成（認証するため）
    var signInManager = this.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
    signInManager.SignIn(user, false, false);

    return RedirectToAction("Index","Home");
}
```

##ログアウト

AccountControllerを利用している場合はAuthenticationManager.SignOut()を呼び出すだけですが、別途実装すする場合は、IAuthenticationManagerを返すヘルパー関数？を用意してあげます（AccountControllerでそうなっているので）。

```csharp
public ActionResult Logoff()
{

    //サインアウト
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
    return RedirectToAction("Index", "Home");
}

//ヘルパー
private IAuthenticationManager AuthenticationManager
{
    get
    {
        return HttpContext.GetOwinContext().Authentication;
    }
}
```

ASP.NET Identiryの基本的な部分はこんな感じです。

##Roleを使う

ここではRoleを利用するために、

* ロールの生成 (CreateRole)
* ユーザー紐付け (CreateAdmin)
* 利用 (AdminOnly)

上記の3つのActionを追加していきます。

###準備

ロールを使うためにはまず、Models/IdentityModel.csに下記を追加します。

```csharp
//for role
public class ApplicationRole : IdentityRole
{

}
```

次に、App_Start/IdentityConfig.csにApplicationRoleManagerクラスを追加します。

```csharp
public class ApplicationRoleManager : RoleManager<ApplicationRole, string>
{
   public ApplicationRoleManager(IRoleStore<ApplicationRole, string> store)
       : base(store)
   {
   }
    public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
   {
       var manager = new ApplicationRoleManager(new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
       return manager;
   }
}
```

さらに、App_Start/Startup.Auth.csに下記を追加します。

```csharp
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
```

###ロールの作成

ロールを追加します。これによりAspNetRolesにadminが追加されます。

```csharp
public ActionResult CreateRole()
{
    //RoleManagerの取得
    var roleManager = this.HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
    //Roleの生成
    roleManager.Create(new ApplicationRole { Name = "admin"});

    return Content("create role");
}
```

###ユーザーの紐付け

次にユーザーにRoleを紐付けます。ここでは既に登録したユーザーにadminを紐付けます。
これによりAspNetUserRolesテーブルにuserIdとroleIdが登録され紐付けられます。

```csharp
public ActionResult CreateAdmin()
{
    //UserManagerの取得
    var userManager = this.HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    //adminにするユーザーの検索
    var user = userManager.Find("test@test.com", "test@test.com");
    //紐づけ
    userManager.AddToRole(user.Id, "admin");

    return Content("create admin");
}
```

###利用

では、Roleを利用して表示やアクセスを制御してみます。

####View

ViweではIsInRole("rolename")を利用して表示の切り替え等を行うことができます。

```html
@if (Request.IsAuthenticated && HttpContext.Current.User.IsInRole("admin"))
{
    <h2>あなたは管理者です。</h2>
}
```

####その他

その他の情報は下記のように取得できます。

```html
<div>Username: @User.Identity.Name</div>
<div>Login: @User.Identity.IsAuthenticated</div>
<div>Is Admin: @User.IsInRole("admin")</div>
```

####Controller

Controllerではアノテーションを[Authorize(Roles ="rolename")]とすることでアクセスを制御することができます。

```csharp
//adminのみにアクセスを許可
[Authorize(Roles ="admin")]
public ActionResult AdminOnly ()
{
    return Content("こんにちはAdminさん。");
}
```

##その他

* ログインに失敗したときにリダイレクトさせる先(/Account/Login)はApp_Start/Auth.cs中で設定されている。
* 前ページに認証を要求するにはFilterConfig.csにfilters.Add(new AuthorizeAttribute());を加える。
* ログインページなど非認証のアクセスが必要なページはControllerの必要な箇所に[AllowAnonymous]を付ける。
* 逆に基本アクセスを許可し、認証が必要な箇所を限定する際は[Authorize]を利用する。

##参考

* http://blog.okazuki.jp/entry/2015/08/20/214212
* http://blog.okazuki.jp/entry/2013/11/10/190811
* http://kaz16a.hatenablog.jp/entry/2014/10/13/000843