しばやん雑記

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

ASP.NET Core 2.1 で未ログイン時にリダイレクトされる条件を調べた

ASP.NET Web API が追加されたときに問題になった、HTTP 401 を返すとリダイレクトに変換される挙動ですが、ASP.NET Core 2.1 でも近い挙動になるようにデフォルトで設定されています。

当時は謎のキーを追加しましたが、結局 ASP.NET 自体にフラグが追加されて対応されました。

ASP.NET Core MVC では以前のように酷い挙動ではないですし、普通に使っていると問題のない挙動になっていますが、それでも特定のケースでは 401 ではなくリダイレクトに変換されます。

少し迷ったので、挙動を調べた結果をメモとして残しておきます。

リダイレクトされるのは Cookie 認証を使う場合

この未ログイン時にログインページへリダイレクトする挙動は主にブラウザで利用している場合を想定しているので、認証にクッキーを使っている場合のみ発動します。

なので JWT Bearer を使うように設定している場合には、常に HTTP 401 が返るようになります。

しかし Twitter や OpenID Connect などを使う場合にはクッキーを組み合わせて使うので、ほとんどの場合はこの条件を満たすことになります。外部に公開する API では JWT Bearer を使うのが良いでしょう。

Challenge と Unauthorized は別

ASP.NET では HTTP 401 を返しても、強制的にリダイレクトにされていましたが、ASP.NET Core では適切な形で実装されています。具体的には ChallengeResult を返した場合はリダイレクトされますが、UnauthorizedResult を返すと HTTP 401 が返ります。

Authorize 属性を付けると、未ログイン時には ChallengeResult が返されているという仕組みです。

// リダイレクトされる
[Authorize]
public IActionResult Test1()
{
    return View();
}

// リダイレクトされる
public IActionResult Test2()
{
    return Challenge();
}

// リダイレクトされない (HTTP 401 になる)
public IActionResult Test3()
{
    return Unauthorized();
}

この辺りの処理を行っているのは Security Middleware なので ASP.NET Core MVC とは切り離されているのが特徴です。最終的には AuthenticationHandler の呼び出しが行われています。

X-Requested-With を付ける

基本的に Web API で HTTP 401 を返す場合には、リダイレクトではなくそのまま返ってほしいです。

一応 ASP.NET Core のデフォルト実装では X-Requested-With ヘッダーに XMLHttpRequest が指定されている場合には、リダイレクトしないようになっています。

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

ログインページにリダイレクトされていたのが、X-Requested-With を付けると HTTP 401 になります。

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

クッキーで認証しつつ Web API を利用する状況はほぼブラウザ経由なので、これでほとんどのケースで意図したとおりの挙動になるはずです。

OnRedirectToLogin を差し替える

基本的にはデフォルトの実装で問題は発生しない思いますが、どうしてもクッキーで認証しつつもログインページにリダイレクトさせたくない場合は OnRedirectToLogin の実装を差し替えて対応します。

デフォルトの実装は X-Requested-With が付いていない場合にリダイレクトするようになっています。

なので、常に 401 を返すように変えてしまえば、全てのリクエストで HTTP 401 が返るようになります。

services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = ctx =>
    {
        ctx.Response.StatusCode = 401;

        return Task.CompletedTask;
    };
});

実際に試すと、リダイレクトされずにちゃんと 401 が返っていることが確認できます。

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

個人的には ASP.NET Core MVC 2.1 で追加された ApiController 属性を指定するとリダイレクトする挙動を自動でオフにしてほしかったですが、現時点で実装するのは地味にめんどくさい感じです。

一応 Issue では話が進んでいて、Feature の追加で API かどうか判別できるようになるかも、ということらしいです。判別できれば OnRedirectToLogin で処理してしまうだけなので簡単です。

2.2 か 3.0 で対応されるっぽいので、リリース後にはこの辺りの悩みとは無縁になるのでしょう。

とはいえ、クッキーで外部 API の認証を行うのは間違っているので、ここは素直に JWT などに移行した方が良いと思います。そちらの方がスマートに解決できます。