2016-05-23 13 views
7

ASP.NETコアのUseJwtBearerAuthenticationミドルウェアでは、受信したJSON WebトークンをAuthorizationヘッダーで簡単に検証できます。クッキーを介して渡されたJWTを検証するにはどうすればよいですか?

ヘッダーではなくクッキー経由でJWTを認証するにはどうすればよいですか? UseCookieAuthenticationのようなものですが、JWTを含むクッキーのものです。

+2

好奇心:それらを流すためにクッキーを使いたい場合、ベアラトークンを使用するポイントは何ですか?クッキーの代わりにベアラトークンを使用することの全ポイントは、XSRF攻撃のようなセキュリティ上の懸念を避けることです。方程式にクッキーを再導入すると、その脅威モデルが再導入されます。 – Pinpoint

+1

@Pinpoint JWTは厳密にはベアラトークンではありません。彼らはベアラヘッダーを介して、またはクッキーを介して使用することができます。私はJWTを使ってステートレスな「セッション」を行っていますが、ブラウザーのサポートがシンプルなので、Cookieに保存しています。 XSSはCookieフラグによって緩和されます。 –

+0

1.定義上、JWTはベアラトークンまたはPoPトークンのいずれかです(最初のケースでは、トークンの正当な所有者であることを証明する必要はありません.2番目のトークンの正当な所有者であることを証明する必要はありません。所有)。 2. JWTを使用して「セッション」を表現し、認証クッキー(それ自体が「セッション」)に格納することは意味をなさない。私は恐れている。 3. XSSはXSRFとは何の関係もなく、まったく異なる脅威です。 – Pinpoint

答えて

7

次のリンクをご覧ください。

https://stormpath.com/blog/token-authentication-asp-net-core

彼らは、XSS攻撃を防ぐために、HTTPのみクッキーにJWTトークンを格納します。

そして、彼らはStartup.csに次のコードを追加することにより、クッキーでJWTトークンを検証:

CustomJwtDataFormat()がここで定義され、そのカスタム形式で
app.UseCookieAuthentication(new CookieAuthenticationOptions 
{ 
    AutomaticAuthenticate = true, 
    AutomaticChallenge = true, 
    AuthenticationScheme = "Cookie", 
    CookieName = "access_token", 
    TicketDataFormat = new CustomJwtDataFormat(
     SecurityAlgorithms.HmacSha256, 
     tokenValidationParameters) 
}); 

public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket> 
{ 
    private readonly string algorithm; 
    private readonly TokenValidationParameters validationParameters; 

    public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters) 
    { 
     this.algorithm = algorithm; 
     this.validationParameters = validationParameters; 
    } 

    public AuthenticationTicket Unprotect(string protectedText) 
     => Unprotect(protectedText, null); 

    public AuthenticationTicket Unprotect(string protectedText, string purpose) 
    { 
     var handler = new JwtSecurityTokenHandler(); 
     ClaimsPrincipal principal = null; 
     SecurityToken validToken = null; 

     try 
     { 
      principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken); 

      var validJwt = validToken as JwtSecurityToken; 

      if (validJwt == null) 
      { 
       throw new ArgumentException("Invalid JWT"); 
      } 

      if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal)) 
      { 
       throw new ArgumentException($"Algorithm must be '{algorithm}'"); 
      } 

      // Additional custom validation of JWT claims here (if any) 
     } 
     catch (SecurityTokenValidationException) 
     { 
      return null; 
     } 
     catch (ArgumentException) 
     { 
      return null; 
     } 

     // Validation passed. Return a valid AuthenticationTicket: 
     return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie"); 
    } 

    // This ISecureDataFormat implementation is decode-only 
    public string Protect(AuthenticationTicket data) 
    { 
     throw new NotImplementedException(); 
    } 

    public string Protect(AuthenticationTicket data, string purpose) 
    { 
     throw new NotImplementedException(); 
    } 
} 

別解決策は、各リクエストをインターセプトするカスタムミドルウェアを作成し、Cookieがあるかどうかを調べ、CookieからJWTを抽出し、コントローラのAuthorizeフィルタに達する前にAuthorizationヘッダーを追加します。

using System.Threading.Tasks; 
using Microsoft.AspNetCore.Http; 
using Microsoft.Extensions.Logging; 

namespace MiddlewareSample 
{ 
    public class JWTInHeaderMiddleware 
    { 
     private readonly RequestDelegate _next; 

     public JWTInHeaderMiddleware(RequestDelegate next) 
     { 
      _next = next; 
     } 

     public async Task Invoke(HttpContext context) 
     { 
      var authenticationCookieName = "access_token"; 
      var cookie = context.Request.Cookies[authenticationCookieName]; 
      if (cookie != null) 
      { 
       var token = JsonConvert.DeserializeObject<AccessToken>(cookie); 
       context.Request.Headers.Append("Authorization", "Bearer " + token.access_token); 
      } 

      await _next.Invoke(context); 
     } 
    } 
} 

... AccessTokenは以下のクラスである:

public class AccessToken 
{ 
    public string token_type { get; set; } 
    public string access_token { get; set; } 
    public string expires_in { get; set; } 
} 

・ホープこのことができます。ここアイデアを得るために、OAuthのトーク​​ンのために働くいくつかのコードです。

注記:このようなやり方(httpのみのクッキー内のトークン)はXSS攻撃の防止に役立ちますが、クロスサイトリクエスト偽造(CSRF)攻撃に対する影響は受けませんので、偽造防止トークンを使用するか、カスタムヘッダーを設定してそれらを防止します。

また、コンテンツのサニタイズを行わないと、攻撃者はHTTPのみのCookieとCRSF保護を有効にしていても、XSSスクリプトを実行してユーザーに代わって要求を行うことができます。ただし、攻撃者は、トークンを含むhttpのみのCookieを盗むことはできません。また、攻撃者は第三者のWebサイトから要求を行うこともできません。

あなたはそれゆえ、まだ...などなど、コメントなどユーザー生成コンテンツに

EDITを重いサニタイズを実行する必要があります。これは、リンクのブログ記事とコードがOP自身によって書かれたことをコメントに書かれていました数日前にこの質問をした。

XSSエクスポージャーを削減する別の「クッキーのトークン」アプローチに興味のある人は、ASP.NET CoreのOpenId Connect ServerなどのoAuthミドルウェアを使用できます。

using System.Security.Claims; 
using System.Threading.Tasks; 
using AspNet.Security.OpenIdConnect.Extensions; 
using AspNet.Security.OpenIdConnect.Server; 
using Newtonsoft.Json; 

namespace Shared.Providers 
{ 
public class AuthenticationProvider : OpenIdConnectServerProvider 
{ 

    private readonly IApplicationService _applicationservice; 
    private readonly IUserService _userService; 
    public AuthenticationProvider(IUserService userService, 
            IApplicationService applicationservice) 
    { 
     _applicationservice = applicationservice; 
     _userService = userService; 
    } 

    public override Task ValidateTokenRequest(ValidateTokenRequestContext context) 
    { 
     if (string.IsNullOrEmpty(context.ClientId)) 
     { 
      context.Reject(
       error: OpenIdConnectConstants.Errors.InvalidRequest, 
       description: "Missing credentials: ensure that your credentials were correctly " + 
          "flowed in the request body or in the authorization header"); 

      return Task.FromResult(0); 
     } 

     #region Validate Client 
     var application = _applicationservice.GetByClientId(context.ClientId); 

      if (applicationResult == null) 
      { 
       context.Reject(
          error: OpenIdConnectConstants.Errors.InvalidClient, 
          description: "Application not found in the database: ensure that your client_id is correct"); 

       return Task.FromResult(0); 
      } 
      else 
      { 
       var application = applicationResult.Data; 
       if (application.ApplicationType == (int)ApplicationTypes.JavaScript) 
       { 
        // Note: the context is marked as skipped instead of validated because the client 
        // is not trusted (JavaScript applications cannot keep their credentials secret). 
        context.Skip(); 
       } 
       else 
       { 
        context.Reject(
          error: OpenIdConnectConstants.Errors.InvalidClient, 
          description: "Authorization server only handles Javascript application."); 

        return Task.FromResult(0); 
       } 
      } 
     #endregion Validate Client 

     return Task.FromResult(0); 
    } 

    public override async Task HandleTokenRequest(HandleTokenRequestContext context) 
    { 
     if (context.Request.IsPasswordGrantType()) 
     { 
      var username = context.Request.Username.ToLowerInvariant(); 
      var user = await _userService.GetUserLoginDtoAsync(
       // filter 
       u => u.UserName == username 
      ); 

      if (user == null) 
      { 
       context.Reject(
         error: OpenIdConnectConstants.Errors.InvalidGrant, 
         description: "Invalid username or password."); 
       return; 
      } 
      var password = context.Request.Password; 

      var passWordCheckResult = await _userService.CheckUserPasswordAsync(user, context.Request.Password); 


      if (!passWordCheckResult) 
      { 
       context.Reject(
         error: OpenIdConnectConstants.Errors.InvalidGrant, 
         description: "Invalid username or password."); 
       return; 
      } 

      var roles = await _userService.GetUserRolesAsync(user); 

      if (!roles.Any()) 
      { 
       context.Reject(
         error: OpenIdConnectConstants.Errors.InvalidRequest, 
         description: "Invalid user configuration."); 
       return; 
      } 
     // add the claims 
     var identity = new ClaimsIdentity(context.Options.AuthenticationScheme); 
     identity.AddClaim(ClaimTypes.NameIdentifier, user.Id, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); 
     identity.AddClaim(ClaimTypes.Name, user.UserName, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); 
     // add the user's roles as claims 
     foreach (var role in roles) 
     { 
      identity.AddClaim(ClaimTypes.Role, role, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken); 
     } 
     context.Validate(new ClaimsPrincipal(identity)); 
     } 
     else 
     { 
      context.Reject(
        error: OpenIdConnectConstants.Errors.InvalidGrant, 
        description: "Invalid grant type."); 
      return; 
     } 

     return; 
    } 

    public override Task ApplyTokenResponse(ApplyTokenResponseContext context) 
    { 
     var token = context.Response.Root; 

     var stringified = JsonConvert.SerializeObject(token); 
     // the token will be stored in a cookie on the client 
     context.HttpContext.Response.Cookies.Append(
      "exampleToken", 
      stringified, 
      new Microsoft.AspNetCore.Http.CookieOptions() 
      { 
       Path = "/", 
       HttpOnly = true, // to prevent XSS 
       Secure = false, // set to true in production 
       Expires = // your token life time 
      } 
     ); 

     return base.ApplyTokenResponse(context); 
    } 
} 
} 

:あなたはトークンをシリアライズし、HTTPのみであるクッキーにそれを保存することができ、クライアントに戻す(ApplyTokenResponseを())トークンを送信するために呼び出されるトークンプロバイダの方法で

次に、各リクエストにクッキーが添付されていることを確認する必要があります。また、クッキーを傍受し、ヘッダに設定するためにいくつかのミドルウェアを作成する必要があります。

public class AuthorizationHeader 
{ 
    private readonly RequestDelegate _next; 

    public AuthorizationHeader(RequestDelegate next) 
    { 
     _next = next; 
    } 

    public async Task Invoke(HttpContext context) 
    { 
     var authenticationCookieName = "exampleToken"; 
     var cookie = context.Request.Cookies[authenticationCookieName]; 
     if (cookie != null) 
     { 

      if (!context.Request.Path.ToString().ToLower().Contains("/account/logout")) 
      { 
       if (!string.IsNullOrEmpty(cookie)) 
       { 
        var token = JsonConvert.DeserializeObject<AccessToken>(cookie); 
        if (token != null) 
        { 
         var headerValue = "Bearer " + token.access_token; 
         if (context.Request.Headers.ContainsKey("Authorization")) 
         { 
          context.Request.Headers["Authorization"] = headerValue; 
         }else 
         { 
          context.Request.Headers.Append("Authorization", headerValue); 
         } 
        } 
       } 
       await _next.Invoke(context); 
      } 
      else 
      { 
       // this is a logout request, clear the cookie by making it expire now 
       context.Response.Cookies.Append(authenticationCookieName, 
               "", 
               new Microsoft.AspNetCore.Http.CookieOptions() 
               { 
                Path = "/", 
                HttpOnly = true, 
                Secure = false, 
                Expires = DateTime.UtcNow.AddHours(-1) 
               }); 
       context.Response.Redirect("/"); 
       return; 
      } 
     } 
     else 
     { 
      await _next.Invoke(context); 
     } 
    } 
} 

設定では()startup.csの:

// use the AuthorizationHeader middleware 
    app.UseMiddleware<AuthorizationHeader>(); 
    // Add a new middleware validating access tokens. 
    app.UseOAuthValidation(); 

あなたはその後、通常は承認属性を使用することができます。

[Authorize(Roles = "Administrator,User")] 

このソリューションは、apiとmvcの両方のアプリケーションで使用できます。 AJAXのためにとしかしフェッチ要求をあなたは、ログインページにユーザーをリダイレクトし、代わりに戻りませんいくつかのカスタムミドルウェアを記述する必要があります401:

public class RedirectHandler 
{ 
    private readonly RequestDelegate _next; 

    public RedirectHandler(RequestDelegate next) 
    { 
     _next = next; 
    } 

    public bool IsAjaxRequest(HttpContext context) 
    { 
     return context.Request.Headers["X-Requested-With"] == "XMLHttpRequest"; 
    } 

    public bool IsFetchRequest(HttpContext context) 
    { 
     return context.Request.Headers["X-Requested-With"] == "Fetch"; 
    } 

    public async Task Invoke(HttpContext context) 
    { 
     await _next.Invoke(context); 
     var ajax = IsAjaxRequest(context); 
     var fetch = IsFetchRequest(context); 
     if (context.Response.StatusCode == 302 && (ajax || fetch)) 
     { 
      context.Response.Clear(); 
      context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; 
      await context.Response.WriteAsync("Unauthorized"); 
      return; 
     } 
    } 
} 
+3

私は尋ねる必要があります、あなたはそのブログ記事の著者が誰だったかチェックしたことがありますか? https://i.ytimg.com/vi/OGAu_DeKckI/hqdefault.jpg – KreepN

+0

あなたは非常に有効なポイントを作っています。私は作者をチェックしませんでした。より客観的な解決策を検討します。 oauth2を使用して同等のカスタム認証の検証を行いましたが、すぐに代替を提供するように編集します。 – Darxtar

+0

Lol、私はまだ気づいていない:あなたは自分のブログの投稿とコードをOPにリンクしています。それが私が求めていたものです。 – KreepN

0
私は成功した(Darxtarの答え)のミドルウェアを実装

// TokenController.cs 

[AllowAnonymous] 
[HttpGet] 
public IActionResult Get([FromQuery]string username, [FromQuery]string password) 
{ 
    ... 

    var tokenString = new JwtSecurityTokenHandler().WriteToken(token); 

    Response.Cookies.Append(
     "x", 
     tokenString, 
     new CookieOptions() 
     { 
      Path = "/" 
     } 
    ); 

    return StatusCode(200, tokenString); 

} 


// JWTInHeaderMiddleware.cs 

public class JWTInHeaderMiddleware 
{ 

    private readonly RequestDelegate _next; 

    public JWTInHeaderMiddleware(RequestDelegate next) 
    { 
     _next = next; 
    } 

    public async Task Invoke(HttpContext context) 
    { 

     var name = "x"; 
     var cookie = context.Request.Cookies[name]; 

     if (cookie != null) 
      if (!context.Request.Headers.ContainsKey("Authorization")) 
       context.Request.Headers.Append("Authorization", "Bearer " + cookie); 

     await _next.Invoke(context); 

    } 

} 

// Startup.cs 

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 

    ... 

    app.UseMiddleware<JWTInHeaderMiddleware>(); 

    ... 

} 
関連する問題