MVC Forms Authentication Timeout

シナリオ:.NET MVC、Forms Authentication にてtime out を60分に設定する。

IISのタイムアウトを65分に設定:
  <system.web>
      <authentication mode="Forms">
      <forms loginUrl="~/Pub/SignIn" defaultUrl="~/List/Index" name="authest" protection="All" path="/" timeout="65" slidingExpiration="true"  cookieless="UseCookies" enableCrossAppRedirects ="true"/>
    </authentication>
    <sessionState mode="InProc" timeout="65" customProvider="DefaultSessionProvider">
  </system.web>

JSでタイムアウトを 60分に設定:
var timeout = {
    timoutPeriod: 60, // in min.
    timerId: null,
    resetTimeOut: function () {
        if (0 < timeout.timoutPeriod) {
            window.clearTimeout(timeout.timerId);
            timeout.timerId = window.setTimeout(function () { timeout.timeOut(); }, timeout.timoutPeriod * 60000);
        }
    },
    timeOut: function () {
        dirty.pauseOnce();
        virLoc("Pub/SignOout");
    }
};


timeout.resetTimeOut();

$(document).ajaxStop(function () {
    timeout.resetTimeOut();
});


基本上でOK。しかしIIS Application Poolがリセットとかの火事はありえる。期限切れ auth cookies にて Requestが来た場合に綺麗に処理したい。

- Page GET request は IIS が Sign in 画面に redirect してくれるので問題なし。

- Page POST が期限切れの場合、少々厄介。Page GET同様、Sign In page への 302 redirect が返るのでそこは問題ない。しかし、そこに RedirectURL が残る。無事 Sign In が終了すると、無論その後の作りによるが、RedirectURL へ redirect する場合も多いだろう。この場合、そのまま Action が実行されると、、、パラメターは既に失われているので、事故となる可能性も全否定できず、幸運でもユーザーさんを悩ますようなものがまちがいなく表示されるは濃厚。POST を期待している Action (通常は CRUDする)には、[HttpPost] を明示しておこう。これを付けていれば、Page Not Found で、事故は防げる。ただし見た目汚い。この解決は後述。

- Ajax request の場合も少々厄介。JS側は Json を期待しているが、期限切れAuthの場合、IISは Page GET と同様、text で 302 redirect を返してしまう。これは$(document).ajaxError には引っかからずに、かつ Browser は実際に redirect を受けて実行する。かなり汚い状態。よって、Ajax request の場合で、期限切れの場合、302 ではなく、明示的なエラーを返すべき(期限切れAjax に対して default behavior がなぜ 302 処理しちゃうのかは不明、MVCの悪仕様か)。よって global.asaxにて:

        protected void Application_EndRequest(object sender, EventArgs args)
        {
            HttpContextWrapper context = new HttpContextWrapper(Context);
            if (context.Response.StatusCode == 302 && !IsAuthorised(context))
                if (context.Request.IsAjaxRequest())
                    context.Response.StatusCode = 401; // to let jQuery know (if 302 no error occures)
                else if (context.Request.RequestType == "POST")
                    context.Response.Redirect("~/Pub/Signin"); // remove redirect url
        }
        bool IsAuthorised(HttpContextWrapper context)
        {
            if (context.User.Identity.IsAuthenticated)
                return true;
            HttpCookie authCookie = context.Response.Cookies.Get(FormsAuthentication.FormsCookieName);
            return authCookie != null && DateTime.Now < authCookie.Expires;
        }

JS側はこれを受けて:
$(document).ajaxError(function (e, jqXHR, settings, thrownError) {
    if (jqXHR.status === 401 || jqXHR.responseText && 0 <= jqXHR.responseText.indexOf("Pub/SignIn"))
        bootbox.alert("Timeout occurred. Please sign in.", function () {
            virLoc("Pub/SignIn"); // this does window.location.href = ....
        });
    else if (jqXHR.responseText)
        bootbox.alert(jqXHR.responseText);
    else if (jqXHR.responseJSON && jqXHR.responseJSON.errors)
        bootbox.alert(jqXHR.responseJSON.errors);
    else
        bootbox.alert("Communication error.");

});

さらに一つ汚くなるものあり、auth 期限切れ File Download request。このままだと、Sign In 成功後、Download の Action に redirect されるので、白紙のページが表示され、File download が始まる。汚いし、Auth 期限が一度切れたのだから、やり直してもらおう。とはいえ、file download は通常 Page GET でくるから、上のように区別付けることが出来ない。まあ、download 目的のAction数は、かなり限られているだろうから、Authentication 成功後の挙動でカバーする。

                ...
                Response.Cookies.Add(idCookie);

                string redirect = FormsAuthentication.GetRedirectUrl(vm.UserId, false);
                if (string.IsNullOrEmpty(redirect)
                    || redirect.ToLower().Contains("signin")
                    || redirect.ToLower().Contains("signout")
                    || redirect.ToLower().Contains("getattachment")
                )
                    return RedirectToAction("Index", "List");
                else

                    return Redirect(redirect);

File Upload は、POST で来るので、if (context.Request.RequestType == "POST") に引っかかるのでOK。



コメント

このブログの人気の投稿

HiddenFor 要注意

SPA を IIS から流す際の ASP 側のルーティング

Jest の テスト・スクリプトをデバッグする術