ASP.NET Core MVC - Cookie認証を試す

ASP.NET Core MVCの勉強の記録です。

こちらのドキュメントを参考にしてIdentityを使わないCookie認証を試しました。

ASP.NET Core Identity なしでの cookie 認証を使用します。 | Microsoft Docs

Cookie認証はいわゆるフォーム認証だと思うんですが、もしかして今はそう言わないのかも。

仕組みや流れを理解するために最小限のサンプルコードを目指します。

Startupクラス

まずはStartupクラスのConfigureServicesメソッドとConfigureメソッドから見てみましょう。

Startup.ConfigureServices

ConfigureServicesメソッドでは、AddAuthenticationメソッドとAddCookieメソッドを使ってクッキー認証に必要なサービスを登録します。

また、AddMvcメソッドを使ってMVCに必要なサービスを登録しつつ、サイト全体にアクセス制御を設定するグローバルフィルタも追加しています。

public void ConfigureServices(IServiceCollection services) {
    // クッキー認証に必要なサービスを登録
    services
        .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options => {
            // クッキーの名前を変える
            options.Cookie.Name = "auth";

            // リダイレクトするログインURLも小文字に変える
            // ~/Account/Login => ~/account/login
            options.LoginPath = CookieAuthenticationDefaults.LoginPath.ToString().ToLower();
        });

    // MVCで利用するサービスを登録
    services.AddMvc(options => {
        // グローバルフィルタに承認フィルタを追加
        // すべてのコントローラでログインが必要にしておく
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    });

    services.Configure<RouteOptions>(options => {
        // URLは小文字にする
        options.LowercaseUrls = true;
    });
}

正直なところAddAuthenticationに渡すスキーマについてはまだしっかり理解していない部分です。 ドキュメントにクッキー認証の複数のインスタンスとあったけど、外部IDでログインする場合も考慮するってこと?

Startup.Configure

Configureメソッドでは、UseAuthenticationメソッドで認証のミドルウェアをパイプラインを追加します。

このミドルウェアは、クッキーをもとに作ったプリンシパルをHttpContext.Userプロパティにセットしてくれるようです。

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
    }

    // パイプラインに認証のミドルウェアを追加する
    // HttpContext.Userをセットしてくれる
    app.UseAuthentication();

    // パイプラインにMVCのミドルウェアを追加する
    app.UseMvcWithDefaultRoute();
}

HomeController

今回のサンプルではコントローラを2つ用意します。

  • HomeController...認証が必要なコントローラ
  • AccountController...認証が不要でログインなどを行うコントローラ

まずは認証が必要なHomeControllerです。 Indexアクションを用意して、ログインしたユーザ名を表示するようにしておきます。

認証はグローバルフィルタで設定してあるので、コントローラに属性を設定する必要はありません。

// ログインが必要なコントローラ
public class HomeController : Controller {
    public IActionResult Index() {
        // ユーザ名を表示する
        // User.Identity.Nameは"user01@example.jp"
        return Content(User.Identity.Name);
    }
}

AccountController

AccountControllerでは3つのアクションを用意します。

  • ログインビューを返すアクション
  • ログイン処理(POST)のアクション
  • ログアウト処理のアクション

1つずつ見ていきましょう。

ログインビューを返すアクションとログインビュー

グローバルフィルタで認証が設定されているので、アクションメソッドにAllowAnonymous属性を指定して匿名アクセスを許可します。

// ログインビュー
[AllowAnonymous]
public IActionResult Login() {
    return View();
}

ビューは本当ならログインIDとパスワードのinput要素があるのかなと思いますが、今回は省略して単純にPOSTするだけにしました。

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>

<html>
<head>
   <meta name="viewport" content="width=device-width" />
   <title>Login</title>
</head>
<body>
    <form asp-action="Login" method="post">
        @* ログインIDやパスワードのinput要素は省略 *@
        <button type="submit">ログイン</button>
    </form>
</body>
</html>
ログイン処理のアクション

ログイン処理のアクションの肝は、プリンシパルを作ってHttpContext.SignInAsyncメソッドを呼び出すところだと思います。

HttpContext.SignInAsyncメソッドを呼び出すと、認証クッキーをレスポンスに追加できます。

// ログインのポスト処理
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(IFormCollection fromValues) {
    // サインインに必要なプリンシパルを作る
    // 本当ならユーザIDとパスワードからユーザを特定して・・・という処理が入るはず
    var claims = new[] {
        // 適当なユーザ名を登録しておく
        new Claim(ClaimTypes.Name, "user01@example.jp"),
    };
    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var principal = new ClaimsPrincipal(identity);

    // サインイン
    // 認証クッキーをレスポンスに追加
    await HttpContext.SignInAsync(principal);

    // ログインが必要なアクションにリダイレクト
    return RedirectToAction("Index", "Home");
}

今回は適当なプリシンパルを作りました。 本当ならログインID/パスワードの検証やDBアクセスなどをすると思いますが省略しています。

ログアウト処理のアクション

最後にログアウト処理です。

HttpContext.SignOutAsyncメソッドを呼び出すと、レスポンスから認証クッキーを削除できます。

// ログアウト
[AllowAnonymous]
public async Task<IActionResult> Logout() {
    // サインアウト
    // 認証クッキーをレスポンスから削除
    await HttpContext.SignOutAsync();

    // ログインビューにリダイレクト
    return RedirectToAction("Login");
}

基本的なクッキー認証はこんな感じかなと思います。

動きとしては、まず~/home/indexにアクセスすると認証されていないので~/account/loginにリダイレクトされます。 ~/account/loginにPOSTするとサインインして~/home/indexにリダイレクトされて、user01@example.jp と表示されるようになります。

おしまい。