ASP.NET MVC - フォーム認証のタイムアウトで、ログインページにリダイレクトしないようにする(.NET 4.5~)

先日 MSDN で偶然このプロパティを見つけました。

HttpResponse.SuppressFormsAuthenticationRedirect プロパティ (System.Web)

思わず、おおーとなりました。

なぜかと言うと、MSDN の解説にも書いてありますが、ASP.NET では、認証されていない状態(認証クッキーのタイムアウトなども)で認証が必要な URL にアクセスすると、ログインページにリダイレクトされます。

この動きは AJAX リクエストだと割とやっかいで、レスポンスに JSON を期待しているのに取得できるのはログインページの HTML なので、スクリプトエラーになったり、部分的な HTML を期待してページの一部を書き換える処理だと、ページの一部がログインページになったりってことがありました。

今まではこの設定がなかったので、ログインのアクションメソッドで AJAX リクエストか判断して小細工をした記憶があります。

ということで、早速このプロパティを試してみました。

ログインページにリダイレクトする動き

まずは、今まで通りのログインページにリダイレクトする動きを確認します。

コントローラ
public class SampleController : Controller {
    // テスト用ページを表示するアクション
    public ActionResult Index() {
        return View();
    }

    // 認証が必要なアクション
    [Authorize]
    public ActionResult Something() {
        return Json(
            new { message = "特に意味のない JSON" },
            JsonRequestBehavior.AllowGet);
    }
}
ビュー(のJavaScript部分)

index.cshtmlにこのスクリプトが書いてあります。

$(function() {
    // 認証が必要なアクションへの AJAX リクエスト
    $.ajax("/sample/something")
        .done(function(data, status, xhr) {
            console.log("done");
        })
        .fail(function(xhr, status, error) {
            console.log("fail:" + status + ", " + error);
        });
});

/sample/indexに対するHTTPの通信を確認すると、リダイレクト(302)が返ってくることがわかります。

f:id:ichiroku11:20130908131241p:plain

XMLHttpRequest は内部でリダイレクトを処理するようで、最終的には$.ajaxdoneに渡したコールバックが呼び出されて、引数dataにはログインページ(/account/login)のHTMLが入ります。

ログインページにリダイレクトしないようにする

今度は、ログインページにリダイレクトしないように、SuppressFormsAuthenticationRedirectプロパティを使ってみます。 設定するタイミングや方法はいろいろあるかなと思いますが、今回は試しにAuthorizeAttributeを継承した属性を作ってみました。

属性とコントローラ
public class SampleAuthorizeAttribute : AuthorizeAttribute {
    // 認証されていないリクエストを処理するメソッドをオーバーライド
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
        var httpContext = filterContext.HttpContext;
        // AJAX リクエストの場合は、ログインページへのリダイレクトを抑制する
        if(httpContext.Request.IsAjaxRequest()) {
            httpContext.Response.SuppressFormsAuthenticationRedirect = true;
        }
        base.HandleUnauthorizedRequest(filterContext);
    }
}

public class SampleController : Controller {
    public ActionResult Index() {
        return View();
    }

    // Authorize 属性から SampleAuthorize 属性に変更
    [SampleAuthorize]
    public ActionResult Something() {
        return Json(
            new { message = "特に意味のない JSON" },
            JsonRequestBehavior.AllowGet);
    }
}

さっきと同じように AJAX リクエストの通信を見てみると、今度はリダイレクトではなく 401 が返ってきます。

f:id:ichiroku11:20130908131304p:plain

また、$.ajaxfailに渡したコールバックが呼び出されて、console.log は以下のように。

fail:error, Unauthorized 

JavaScript で素直にエラー処理を書けるようになるので素敵ですね。