web-dev-qa-db-ja.com

ASP.NET Core WebAPI Cookie + JWT認証

aPIバックエンド(ASP.NET Core WebAPI)を備えたSPA(Angular)があります。

SPAはapp.mydomain.comでリッスンし、APIはapp.mydomain.com/APIでリッスンします

組み込みのMicrosoft.AspNetCore.Authentication.JwtBearer;を使用して認証にJWTを使用します。トークンを作成するコントローラーapp.mydomain.com/API/auth/jwt/loginがあります。 SPAはそれらをローカルストレージに保存します。すべてが完璧に機能します。セキュリティ監査の後、ローカルストレージをCookieに切り替えるように言われました。

問題は、app.mydomain.com/APIのAPIがSPAによって使用されるだけでなく、モバイルアプリと複数の顧客のサーバー2サーバーソリューションによっても使用されることです。

そのため、JWTをそのまま保持する必要がありますが、Cookieを追加する必要があります。異なるコントローラーでCookieとJWTを組み合わせた記事をいくつか見つけましたが、各コントローラーで並行して動作する必要があります。

クライアントがCookieを送信する場合、Cookieを介して認証します。クライアントがJWTベアラを送信する場合、JWTを介して認証します。

これは、組み込みのASP.NET認証またはDIYミドルウェアを介して達成できますか?

ありがとう!

6
Luke1988

私は同じ問題を抱えていましたが、ここでstackoverflowの別の質問で解決策と思われるものを見つけました。

this をご覧ください。

その解決策を自分で試して、この回答を結果で更新します。

編集:同じ方法で二重認証タイプを達成することは不可能のようですが、私が言及したリンクで提供されているソリューションは言います:

2つのScheme Or-Likeを使用してメソッドを承認することはできませんが、2つのパブリックメソッドを使用してプライベートメソッドを呼び出すことができます

//private method
private IActionResult GetThingPrivate()
{
   //your Code here
}
//Jwt-Method
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme}")]
[HttpGet("bearer")]
public IActionResult GetByBearer()
{
   return GetThingsPrivate();
}
 //Cookie-Method
[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme}")]
[HttpGet("cookie")]
public IActionResult GetByCookie()
{
   return GetThingsPrivate();
}    

とにかくリンクを見てください、それは確かに私を助けました。答えはニコラウスの功績です。

これを行うための良い方法に関する多くの情報を見つけることができませんでした-APIを複製しなければならないのは、2つの認証スキームをサポートするだけの苦痛です。

私はリバースプロキシを使用するアイデアを検討してきましたが、これには良い解決策のように思えます。

  1. ユーザーはWebサイトにサインインします(セッションにはCookie httpOnlyを使用します)
  2. ウェブサイトは偽造防止トークンを使用しています
  3. SPAはリクエストをWebサイトサーバーに送信し、ヘッダーに偽造防止トークンを含めます: https://app.mydomain.com/api/secureResource
  4. Webサイトサーバーは偽造防止トークン(CSRF)を検証します
  5. ウェブサイトサーバーは、リクエストがAPI用であると判断し、リバースプロキシに送信する必要があります
  6. Webサイトサーバーは、APIのユーザーアクセストークンを取得します
  7. リバースプロキシはリクエストをAPIに転送します: https://api.mydomain.com/api/secureResource

偽造防止トークン(#2、#4)は重要であるか、APIをCSRF攻撃にさらす可能性があることに注意してください。


例(IdentityServer4を使用した.NET Core 2.1 MVC):

この実用的な例を取得するために、IdentityServer4クイックスタートで開始しました ハイブリッドフローへの切り替えとAPIアクセスの追加 。これは、MVCアプリケーションがCookieを使用し、IDサーバーからaccess_tokenを要求してAPIを呼び出すことができるシナリオを設定します。

Microsoft.AspNetCore.Proxy をリバースプロキシに使用し、クイックスタートを変更しました。

MVC Startup.ConfigureServices:

services.AddAntiforgery();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

MVC Startup.Configure:

app.MapWhen(IsApiRequest, builder =>
{
    builder.UseAntiforgeryTokens();

    var messageHandler = new BearerTokenRequestHandler(builder.ApplicationServices);
    var proxyOptions = new ProxyOptions
    {
        Scheme = "https",
        Host = "api.mydomain.com",
        Port = "443",
        BackChannelMessageHandler = messageHandler
    };
    builder.RunProxy(proxyOptions);
});

private static bool IsApiRequest(HttpContext httpContext)
{
    return httpContext.Request.Path.Value.StartsWith(@"/api/", StringComparison.OrdinalIgnoreCase);
}

ValidateAntiForgeryToken (マリウス・シュルツ):

public class ValidateAntiForgeryTokenMiddleware
{
    private readonly RequestDelegate next;
    private readonly IAntiforgery antiforgery;

    public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
    {
        this.next = next;
        this.antiforgery = antiforgery;
    }

    public async Task Invoke(HttpContext context)
    {
        await antiforgery.ValidateRequestAsync(context);
        await next(context);
    }
}

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app)
    {
        return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
    }
}

BearerTokenRequestHandler:

public class BearerTokenRequestHandler : DelegatingHandler
{
    private readonly IServiceProvider serviceProvider;

    public BearerTokenRequestHandler(IServiceProvider serviceProvider, HttpMessageHandler innerHandler = null)
    {
        this.serviceProvider = serviceProvider;
        InnerHandler = innerHandler ?? new HttpClientHandler();
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
        var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
        request.Headers.Authorization =new AuthenticationHeaderValue("Bearer", accessToken);
        var result = await base.SendAsync(request, cancellationToken);
        return result;
    }
}

_Layout.cshtml:

@Html.AntiForgeryToken()

次に、SPAフレームワークを使用してリクエストを行うことができます。確認するために、単純なAJAXリクエストを行っただけです:

<a onclick="sendSecureAjaxRequest()">Do Secure AJAX Request</a>
<div id="ajax-content"></div>

<script language="javascript">
function sendSecureAjaxRequest(path) {
    var myRequest = new XMLHttpRequest();
    myRequest.open('GET', '/api/secureResource');
    myRequest.setRequestHeader("RequestVerificationToken",
        document.getElementsByName('__RequestVerificationToken')[0].value);
    myRequest.onreadystatechange = function () {
        if (myRequest.readyState === XMLHttpRequest.DONE) {
            if (myRequest.status === 200) {
                document.getElementById('ajax-content').innerHTML = myRequest.responseText;
            } else {
                alert('There was an error processing the AJAX request: ' + myRequest.status);
            }
        }  
    };
    myRequest.send();
};
</script>

これは概念実証テストであったため、走行距離は非常に長く、.NET Coreおよびミドルウェアの構成はかなり新しいので、おそらく見栄えがよくなります。これで限定的なテストを行い、APIへのGETリクエストのみを行い、SSL(https)は使用しませんでした。

予想どおり、偽造防止トークンがAJAX要求から削除されると失敗します。ユーザーがログイン(認証)されていない場合、要求は失敗します。

いつものように、各プロジェクトは一意であるため、セキュリティ要件が満たされていることを常に確認してください。誰かが提起する可能性のあるセキュリティ上の懸念については、この回答に残っているコメントをご覧ください。

別の注意事項として、一般的に使用されるすべてのブラウザーでサブリソースの整合性(SRI)とコンテンツセキュリティポリシー(CSP)が利用可能になると(つまり、古いブラウザーは段階的に廃止されます)、ローカルストレージを再評価して保存する必要がありますトークンストレージの複雑さを軽減するAPIトークン。SRIとCSPを使用して、ブラウザをサポートするための攻撃対象領域を減らします。

3
Mike Rowley