しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC で HTTP 401 を返した時にログインページへリダイレクトさせない方法

割と有名な ASP.NET のフォーム認証モジュールのおせっかい機能として、HTTP ステータスコードで 401 を返すと自動的にログインページへの 302 リダイレクトに変換するというのがあります。

認証が必要な Web サイトを作る場合には割と便利なんですが、API を MVC でサクッと作りたい場合には厄介です。API としては 401 を返したいのに、勝手にログインページへのリダイレクトになるからです。

f:id:shiba-yan:20150612140747p:plain

ちなみに ASP.NET Web API の Nightly 版でも同じ問題がありましたが、製品版ではアプリケーション設定に特殊なキーを追加することで回避可能になりました。

最初から Web API で作っとけよという感じですが、今回久しぶりにはまったので書きます。

ASP.NET 4.5 から HttpResponse に SuppressFormsAuthenticationRedirect というそのまんまな名前のプロパティが追加されています。

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

このプロパティを true にすればリダイレクトは回避出来るんですが、このプロパティに値をセットするタイミングで少し悩みました。

最初は OnActionExecuting をオーバーライドして設定すればいいだろと思って試しました。

public class ApiController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Response.SuppressFormsAuthenticationRedirect = true;

        base.OnActionExecuting(filterContext);
    }

    [Authorize]
    public ActionResult Auth()
    {
        return Json(true, JsonRequestBehavior.AllowGet);
    }
}

ところが実際に試すとリダイレクトされたままです。

少し考えると、そもそも Authorize 属性を付けた場合には OnActionExecuting が実行される前に処理が打ち切られてしまうので、そもそも呼び出されないことに気が付きました。

public class ApiController : Controller
{
    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
        requestContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;

        base.Initialize(requestContext);
    }

    [Authorize]
    public ActionResult Auth()
    {
        return Json(true, JsonRequestBehavior.AllowGet);
    }
}

必ず呼び出されるメソッドを考えた結果、今回は Initialize をオーバーライドすることにしました。

f:id:shiba-yan:20150612140759p:plain

これで 401 を返してもリダイレクトされることが無くなりました。