ASP.NET MVC + Web API で フォーム認証 の実装

0 件のコメント

"技術的にできる" という実践例として MVC 4 + WebAPI という組み合わせで、 RESTful like なフォーム認証を作る方法を記載します。 MVC だけで実装する フォーム認証 と比べ、実装量が増える デメリット がありますが、操作感が良くなる(リクエスト時に固まるをなくせる)メリットはあると思います。

目次

サンプルコード

概要

ちょっと強引かもしれませんが、MVC4 で画面表示のみ行い、ログイン と ログアウト を ASP.NET WebAPI で行う実装をしてみます。 完成時の挙動を下記のシーケンス図に示します。

  1. サイトへアクセス
  2. 未認証なので、ログインページ へ リダイレクト
  3. ユーザーID、パスワード を Ajax リクエスト
  4. 認証結果の返信し、認証済みのクッキーを設定
  5. もともとアクセスしようとしたページへ リダイレクト

上記について、具体的には次のような実装を行います。 2 のリダイレクトは web.config で実現します。 3 のログイン処理は jQuery を使って、 Ajax リクエスト を行います。 5 のリダイレクトは JavaScript で実現します。

以下では、それぞれについて詳細を掲載します。

作成する ファイル 、 フォルダ 構成

MVC4 + WebAPI で フォーム認証 を行おうとしたとき、以下のようなファイルを作っていきます。 今回は、それなりにコード記述が必要ですが…ブログ中では重要部分のみで、詳細は割愛します。

  • /web.config
    認証と承認の設定を記述します。
  • /Controllers/AuthenticationController.cs
    認証の実処理を行います。
  • /Controllers/HomeController.cs
    秘匿するページ。
  • /Controllers/UserController.cs
    ログインページ。

web.config の認証設定

Authentication (認証) は フォーム認証 で設定します。 Authorization (承認) は ホワイトリスト方式 (許可するものだけを記載する方式) で設定したいので、 ルートですべて拒否し、認証処理に必要な部分だけ許可するように設定します。

web.config (一部抜粋)

<configuration>

  <system.web>
    <authentication mode="Forms">
      <forms loginUrl="/User/Login"></forms>
    </authentication>
    <authorization>
      <deny users="?"/>
    </authorization>
  </system.web>

  <location path="Scripts">
    <system.web>
      <authorization>
        <allow users="*"/>
      </authorization>
    </system.web>
  </location>

  <location path="api/authentication">
    <system.web>
      <authorization>
        <allow users="*"/>
      </authorization>
    </system.web>
  </location>

</configuration>

ログイン、ログアウト 処理

ログイン、ログアウトは WebAPI で実装します。 以下には WebAPI の実装を掲載します。 やっていることは、受け取ったID、パスワードが正しいか判定して、正しければクッキーを発行する処理です。 認証済みであることを示すクッキーの発行、削除には FormsAuthentication クラス を利用します。

AuthenticationController.cs

namespace MvcApplication.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using System.Web.Security;
    using Models;

    public class AuthenticationController : ApiController
    {
        //
        // POST: /api/authentication/signin

        [HttpPost]
        public HttpResponseMessage SignIn(UserModel model)
        {
            if (model != null && model.Id == "hoge" && model.Password == "hoge")
            {
                FormsAuthentication.SetAuthCookie(model.Id, model.RememberMe);

                return this.Request.CreateResponse(HttpStatusCode.OK, new LoginResultModel()
                {
                    IsSuccess = true,
                    Message = "ログインに成功しました。"
                });
            }
            else
            {
                return this.Request.CreateResponse(HttpStatusCode.Accepted, new LoginResultModel()
                {
                    IsSuccess = false,
                    Message = "ID または パスワード が違います。"
                });
            }
        }


        //
        // POST: /api/authentication/signout

        [HttpPost]
        public HttpResponseMessage SignOut()
        {
            FormsAuthentication.SignOut();
            return this.Request.CreateResponse(HttpStatusCode.OK);
        }

    }
}

ログイン 処理への Ajaxリクエスト

ログイン、ログアウト処理は POST で受け付けるように構成したので、 jQuery でも POST で ログイン の リクエスト を行います。 ログイン リクエスト を行うと同時に、画面をリクエスト中である表示に切り替えます。 ここでは、複数回のリクエストを行わせないようにするため、 input要素 を無効化(非活性)にする処理のみ入れています。 画面上に「読み込み中」表示など追加で行っても良いと思います。

index.js (一部抜粋)

/**
* submit ボタン 押下時に呼び出されます。
* @param    {jQuery.Event}  event   jQuery イベントオブジェクト
*/
var submitbutton_onclick = function (event) {
    // ログイン リクエスト
    $.ajax({
        type: 'POST',
        url: '/api/authentication/signin',
        data: {
            id: $('#id').val(),
            password: $('#password').val(),
            rememberMe: $('#rememberMe').prop('checked')
        },
        success: ajax_onsuccess,
        error: ajax_onerror
    });

    // input要素 を無効化
    $('input').attr('disabled', 'disabled');
};

ログイン 後の リダイレクト

ログイン リクエスト が正常に返ってきた場合、所定の場所へ リダイレクト を行います。 リダイレクト先 は、クエリパラメター の ReturnUrl に記載があるので、その値の場所へ window.location を移動させます。

※ クエリ文字列の分解は こちら に記載しました。

index.js (一部抜粋)

/**
* Ajax 通信が成功した際に呼び出されます。
* @param    {object}    data        dataType に従って変換されたオブジェクト
* @param    {string}    textStatus  ステータス文字列
* @param    {jqXHR}     jqXHR       XmlHttpRequest のラッパーオブジェクト
*/
var ajax_onsuccess = function (data, textStatus, jqXHR) {
    if (data.isSuccess) {
        // ログイン成功なので、リダイレクト
        window.location = query.ReturnUrl || '/';
    } else {
        // ログイン失敗なので、input要素 を再度 有効化
        $('input').removeAttr('disabled');
    }
};