امنیت برنامه‌های ASP.NET Core 2.0 با JWT ها

بر خلاف نسخه قبلی، ASP.NET Core 2 پشتیبانی بومی (native) از JSON Web Tokenها را فراهم می‌کند. این امر به ما اجازه می‌دهد تا این تکنولوژی را در برنامه‌های ASP.NET به راحتی ادغام کنیم. در این مقاله نگاهی به نحوه فعال کردن JWTها هنگام ایجاد برنامه‌های Web API مبتنی بر ASP.NET Core 2 خواهیم انداخت.

امنیت برنامه‌های ASP.NET Core 2.0 با JWT ها

کد نهایی را می‌توانید در GitHub  پیدا کنید.

معرفی سریع JWTها

JSON Web Token که اغلب با JWT کوتاه می‌شود، محبوبیت زیادی در محیط وب دارد. این تکنولوژی یک استاندارد باز است که اجازه می دهد داده‌ها بین بخش‌های مختلف به عنوان شیء JSON در یک روش فشرده و ایمن انتقال یابد. آن‌ها معمولا در سناریوهای احراز هویت و تبادل اطلاعات استفاده می‌شوند،‌ زیرا داده‌های فرستاده شده بین یک منبع و یک هدف به صورت دیجیتالی امضاء شده‌اند تا بتوانند به راحتی مورد تأیید و اعتماد قرار گیرند.

JWTها از سه بخش تشکیل می‌شوند:

Header: این یک شیء JSON است که شامل متا اطلاعاتی (meta-information) درباره نوع JWT و الگوریتم هش است که برای رمزگذاری داده‌ها استفاده می‌شود.

Payload: این یک شیء JSON است که شامل داده‌های واقعی مشترک بین منبع و هدف است؛ این داده‌ها در claims کدگذاری شده‌اند که بیانات مربوط به یک موجودیت، معمولا کاربر، است.

Signature: از آنجایی که این بخش نشان‌دهنده یک امضای دیجیتالی بر اساس دو بخش قبلی است، برای اطمینان از صحت داده‌ها می‌باشد.

سه بخش JWT درون رشته‌های Base64 متوالی که توسط نقطه از هم جدا شده اند، با هم ترکیب می‌شوند، تا داده‌ها بتوانند به راحتی در محیط‌های مبنی بر HTTP ارسال شوند. هنگامی که در احراز هویت استفاده می‌شود، تکنولوژی JWT به کلاینت اجازه می‌دهد تا داده‌های سشن (session) را در کنار آن نگهداری کند و هر بار که سعی در دسترس به یک منبع محافظت شده دارد، توکن (علامت رمز) را به سرور ارائه می‌دهد. معمولا توکن در مجوز (Authorization) هدر HTTP با استفاده از ساختار حامل (Bearer schema) به سرور ارسال می‌شود، و باید حاوی تمام اطلاعاتی باشد که اجازه دسترسی یا عدم دسترسی به منابع را می‌دهد.

البته این یک بررسی سریع و کلی از JWT است، که فقط یک اصطلاح رایج و ایده اساسی در مورد این تکنولوژی دارد.

JSON Web Tokenها شیوه فشرده و جامعی برای انتقال امن اطلاعات بین طرفین به عنوان یک شیء JSON هستند.

امنیت برنامه‌های ASP.NET Core 2.0 با JWTها

بیایید نگاهی به نحوه راه‌اندازی یک برنامه ASP.NET Core 2 با پشتیبانی JWT توسط ایجاد برنامه Web API بیندازیم. می‌توانید با استفاده از ویژوال استودیو یا از طریق خط فرمان آن را ایجاد کنید. در اولین مرحله باید قالب پروژه ASP.NET Core Web Application را انتخاب کنید، همانطور که در تصویر زیر نشان داده شده است:

سپس باید نوع برنامه ASP.NET را انتخاب کنید،‌ که برای ما Web API است، همانطور که در تصویر زیر می‌بینید:

برای سادگی ما هیچ نوع احراز هویتی را فعال نکردیم زیرا می‌خواهیم روی مدیریت JWT تمرکز کنیم.

اگر ترجیح می‌دهید برنامه خود را از خط فرمان ایجاد کنید،‌ می‌توانید این کار را از طریق دستور زیر انجام دهید:

dotnet new webapi -n JWT

این دستور یک پروژه ASP.NET Web API با نام JWT در پوشه جاری ایجاد خواهد کرد.

صرف‌ نظر از شیوه‌ای که پروژه خود را با آن ایجاد کرده‌اید، در پوشه فایل‌ها، تعریف کلاس‌ها برای راه‌اندازی یک برنامه پایه ASP.NET Core 2 Web API را دریافت خواهید کرد.

اول از همه بدنه متد ConfigureServices را در Startup.cs تغییر می دهیم تا پیکربندی برای احراز هویت مبنی بر JWT را پشتیبانی کند. کد زیر نتیجه پیاده‌سازی ConfigureServices است:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace JWT
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
          options.TokenValidationParameters = new TokenValidationParameters
          {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Issuer"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
          };
        });

      services.AddMvc();
    }
  }
}

در اینجا با استفاده از متد AddAuthentication و تعیین JwtBearerDefaults.AuthenticationScheme، ساختار احراز هویت JWT را ثبت می‌کنیم. سپس ساختار احراز هویت را با گزینه‌هایی برای حامل JWT پیکربندی می‌کنیم. به طور خاص، تعیین می‌کنیم کدام پارامترها باید درون اکانت گرفته شوند تا یک JSON Web Token معتبر در نظر گرفته شود. کد ما می‌گوید برای در نظر گرفتن یک توکن معتبر باید:

1. سروری که آن توکن را ایجاد کرده است را معتبر سازید. (ValidateIssuer = true)

2. اطمینان حاصل کنید که گیرنده توکن مجاز به دریافت آن است. (ValidateAudience = true)

3. بررسی کنید که توکن منقضی نشده باشد و کلید امضای صادرکننده معتبر باشد. (ValidateLifetime = true)

4. تأیید کنید که کلید مورد استفاده برای امضای توکن ورودی بخشی از لیست کلیدهای قابل اعتماد باشد. (ValidateIssuerSigningKey = true)

علاوه‌ بر این، مقادیر صادرکننده، مخاطب و کلید امضاء شده را مشخص می‌کنیم. می‌توانیم این مقادیر را در فایل appsettings.json ذخیره کنیم تا آن‌ها را از طریق شیء Configuration در دسترس قرار دهیم:

//appsettings.json
{
// ...
  "Jwt": {
    "Key": "veryVerySecretKey",
    "Issuer": "http://localhost:63939/"
  }
}

این مرحله سرویس احراز هویت مبنی بر JWT را پیکربندی می‌کند. برای اینکه سرویس احراز هویت برای برنامه در دسترس باشد، باید متد Configure را در کلاس Startup ایجاد کنیم تا app.UseAuthentication() را فراخوانی کنیم:

// other methods
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseAuthentication();

    app.UseMvc();
}

این تغییر پیکربندی برنامه ما را برای پشتیبانی احراز هویت بر اساس JWT تکمیل می‌کند.

ASP.NET Core 1.0 در مقایسه با ASP.NET Core 2.0

اگر شما از قبل نحوه پشتیبانی ASP.NET Core 1.x از JWT را می دانید، متوجه می‌شوید که ساده‌تر شده است.

اول از همه، در نسخه‌های قبلی ASP.NET Core نیاز به نصب چند پکیج خارجی داشتید، اما حالا نیاز نیست زیرا JSON Web Tokenها بصورت بومی پشتیبانی می‌شوند.

علاوه بر این، مراحل پیکربندی به عنوان نتیجه سیستم کلی احراز هویت ساده شده است. در حقیقت، در حالی که در ASP.NET Core 1.0 ما یک میان‌افزار برای پشتیبانی از ساختار احراز هویت داشتیم، ASP.NET Core 2.0 با استفاده از یک میان‌افزار واحد تمام احراز هویت‌ها را مدیریت می‌کند و هر ساختار احراز هویت به عنوان یک سرویس ثبت شده است.

این روند به ما اجازه می دهد تا یک کد تمیزتر و فشرده‌تر ایجاد کنیم.

امنیت Endpointهای ASP.NET Core 2.0 با JWTها

بعد از اینکه احراز هویت JWT را فعال کردیم، یک Web API ساده را ایجاد می‌کنیم تا لیستی از کتاب‌ها را با یک درخواست HTTP GET فراخوانی کنیم. این API توسط یک کلاس جدید به نام BooksController در فضای نام Controllers نگه داشته می شود.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;

namespace JWT.Controllers
{
  [Route("api/[controller]")]
  public class BooksController : Controller
  {
    [HttpGet, Authorize]
    public IEnumerable<Book> Get()
    {
      var currentUser = HttpContext.User;
      var resultBookList = new Book[] {
        new Book { Author = "Ray Bradbury",Title = "Fahrenheit 451" },
        new Book { Author = "Gabriel García Márquez", Title = "One Hundred years of Solitude" },
        new Book { Author = "George Orwell", Title = "1984" },
        new Book { Author = "Anais Nin", Title = "Delta of Venus" }
      };

      return resultBookList;
    }

    public class Book
    {
      public string Author { get; set; }
      public string Title { get; set; }
      public bool AgeRestriction { get; set; }
    }
  }
}

همانطور که می‌بینید API به سادگی آرایه‌ای از کتاب‌ها را باز می‌گرداند. با این حال، همان‌طور که ما API رابا اتربیوت Authorize مشخص کرده‌ایم، درخواست‌هایی که به این endpoint منتقل می‌شوند، بررسی اعتبارسنجی توکن را با درخواست HTTP ارسال می‌کنند.

اگر برنامه را اجرا کنیم (از طریق IDE یا با دستور dotnet run) و یک درخواست GET به /api/books ایجاد کنیم، یک کد وضعیت HTTP 401 به عنوان پاسخ دریافت خواهیم کرد. می‌توانید آن را با اجرای تست UnAuthorizedAccess در پروژه Test اتچ شده به سورس کد پروژه یا با استفاده از یک HTTP client جنریک مثل curl یا Postman امتحان کنید.

البته این نتیجه ناشی از فقدان توکن است، به طوری که دسترسی به API غیرقابل دسترس شده است.

ایجاد JWT در احراز هویت

بیایید یک API احراز هویت به برنامه خود اضافه کنیم تا کاربر بتواند برای دریافت JWTهای جدید اعتبارسنجی کند. برای انجام این کار، یک کنترلر به نام TokenController در فضای نام Controllers با کد زیر ایجاد کنید:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace JWT.Controllers
{
  [Route("api/[controller]")]
  public class TokenController : Controller
  {
    private IConfiguration _config;

    public TokenController(IConfiguration config)
    {
      _config = config;
    }

    [AllowAnonymous]
    [HttpPost]
    public IActionResult CreateToken([FromBody]LoginModel login)
    {
      IActionResult response = Unauthorized();
      var user = Authenticate(login);

      if (user != null)
      {
        var tokenString = BuildToken(user);
        response = Ok(new { token = tokenString });
      }

      return response;
    }

    private string BuildToken(UserModel user)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(_config["Jwt:Issuer"],
          _config["Jwt:Issuer"],
          expires: DateTime.Now.AddMinutes(30),
          signingCredentials: creds);

        return new JwtSecurityTokenHandler().WriteToken(token);
     }

     private UserModel Authenticate(LoginModel login)
     {
        UserModel user = null;

        if (login.Username == "mario" && login.Password == "secret")
        {
            user = new UserModel { Name = "Mario Rossi", Email = "mario.rossi@domain.com"};
        }
        return user;
     }

    public class LoginModel
    {
      public string Username { get; set; }
      public string Password { get; set; }
    }

    private class UserModel
    {
      public string Name { get; set; }
      public string Email { get; set; }
      public DateTime Birthdate { get; set; }
    }
  }
}

اولین چیزی که باید به آن توجه کنیم اتربیوت AllowAnonymous است. این مورد خیلی مهم است زیرا باید یک API عمومی (public) باشد، این APIای است که هر کسی می‌تواند پس از ارائه گواهی‌های خود به یک توکن جدید دسترسی پیدا کند.

API به درخواست HTTP POST پاسخ می‌دهد و انتظار دارد شیء حاوی نام کاربری و رمز عبور (یک شیء LoginModel) باشد.

متد Authenticate تأیید می‌کند که نام کاربری و رمز عبور ارائه شده، همان‌هایی هستند که انتظار می‌رود و شیء UserModel را به نمایندگی از کاربر باز می‌گرداند. البته این یک پیاده‌سازی جزئی از فرآیند احراز هویت است. همان‌طور که همه ما می‌دانیم اجرای کامل باید دقیق‌تر باشد.

اگر متد Authentication یک کاربر را بازگرداند، گواهی‌های ارائه شده معتبر هستند، API از طریق متد BuildToken یک توکن جدید تولید می‌کند و این جالب‌ترین بخش است: در اینجا یک JSON Web Token را با استفاده از کلاس JwtSecurityToken ایجاد می‌کنیم. چند پارامتر را به سازنده کلاس ارسال می‌کنیم، مانند صادرکننده (issuer)،‌ مخاطب (audience) (هر دو یکسان هستند)، تاریخ انقضاء و زمان و امضاء. در نهایت، متد BuildToken توکن را به عنوان یک رشته،‌ با تبدیل آن از طریق متد WriteToken از کلاس JwtSecurityTokenHandler باز می‌گرداند.

احراز هویت برای دسترسی به APIها

اکنون می توانیم دو APIای که ایجاد کرده‌ایم را تست کنیم.

اول اجازه دهید یک JWT را با ایجاد درخواست HTTP POST به endpointی /api/token فراهم کرده و JSON زیر را در بدنه درخواست ارسال کنیم:

{"username": "mario", "password": "secret"}

این کار می‌تواند به راحتی با Postman یا هر HTTP clientای انجام شود. مثلا با curl دستور زیر می‌شود:

curl -X POST -H 'Content-Type: application/json' \
  -d '{"username": "mario", "password": "secret"}' \
  0:5000/api/token

به عنوان پاسخ یک JSON مانند دستور زیر به دست خواهیم آورد:

{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNYXJpbyBSb3NzaSIsImVtYWlsIjoibWFyaW8ucm9zc2lAZG9tYWluLmNvbSIsImJpcnRoZGF0ZSI6IjE5ODMtMDktMjMiLCJqdGkiOiJmZjQ0YmVjOC03ZDBkLTQ3ZTEtOWJjZC03MTY4NmQ5Nzk3NzkiLCJleHAiOjE1MTIzMjIxNjgsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjM5MzkvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo2MzkzOS8ifQ.9qyvnhDna3gEiGcd_ngsXZisciNOy55RjBP4ENSGfYI"
}

اگر به مقدار توکن نگاه کنیم، سه بخش جدا شده با نقطه را می بینیم، همان‌طور که در ابتدای این مقاله بحث شده است.

اکنون مجددا سعی می کنیم لیستی از کتاب‌ها را همانند بخش قبل درخواست دهیم. با این حال، حالا ما توکن را به عنوان یک HTTP header احراز هویت ارائه خواهیم کرد.

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNYXJpbyBSb3NzaSIsImVtYWlsIjoibWFyaW8ucm9zc2lAZG9tYWluLmNvbSIsImJpcnRoZGF0ZSI6IjE5ODMtMDktMjMiLCJqdGkiOiJmZjQ0YmVjOC03ZDBkLTQ3ZTEtOWJjZC03MTY4NmQ5Nzk3NzkiLCJleHAiOjE1MTIzMjIxNjgsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjM5MzkvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo2MzkzOS8ifQ.9qyvnhDna3gEiGcd_ngsXZisciNOy55RjBP4ENSGfYI

باز هم می‌توان این کار را به راحتی با Postman یا هر HTTP clientای انجام داد. در curl این دستور می‌تواند همانند دستور زیر باشد:

curl -H 'Authorization: Bearer '$JWT 0:5000/api/books

البته، متغیر JWT باید با توکن دریافت شده در هنگام ورود تنظیم شود: JWT="eyJhbG..."

این بار لیست کتاب‌ها را دریافت خواهیم کرد.

مدیریت JWT Claims در ASP.NET Core 2.0

هنگام معرفی JWTها گفتیم توکن ممکن است حاوی اطلاعاتی به نام Claims باشد. این اطلاعات معمولا در مورد کاربر است که می‌تواند هنگام اجازه دسترسی به یک منبع مفید باشد. Claims می‌تواند مثلا ایمیل کاربر، جنسیت، نقش، شهر یا هر اطلاعات سودمند دیگری باشد تا هنگام دسترسی به منابع برای کاربران تبعیض قائل شود. می‌توانیم Claims را در JWT اضافه کنیم تا هنگام بررسی مجوز دسترسی به یک منبع در دسترس باشند. بیایید در عمل نحوه مدیریت Claims را در برنامه ASP.NET Core 2 خود بررسی کنیم.

فرض کنید لیست ما شامل کتاب‌هایی است که برای همه مناسب نیست. مثلا شامل کتاب‌هایی برای رده‌های سنی خاصی است. ما باید بعد از احراز هویت، اطلاعات مربوط به سن کاربر را در JWT وارد کنیم. برای انجام این کار بیایید متد BuildToken از TokenController  را به صورت زیر آپدیت کنیم:

private string BuildToken(UserModel user)
{

    var claims = new[] {
        new Claim(JwtRegisteredClaimNames.Sub, user.Name),
        new Claim(JwtRegisteredClaimNames.Email, user.Email),
        new Claim(JwtRegisteredClaimNames.Birthdate, user.Birthdate.ToString("yyyy-MM-dd")),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
    };

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(_config["Jwt:Issuer"],
      _config["Jwt:Issuer"],
      claims,
      expires: DateTime.Now.AddMinutes(30),
      signingCredentials: creds);

    return new JwtSecurityTokenHandler().WriteToken(token);
}

تفاوت‌های اصلی در رابطه با نسخه‌های قبلی مربوط به تعریف متغیر Claims است. این آرایه‌ای از نمونه‌های Claims است، که هر کدام از یک کلید و یک مقدار ایجاد شده‌اند. کلیدها مقادیری از یک ساختار هستند (JwtRegisteredClaimNames) که نام‌ها را برای Claims استاندارد عمومی ارائه می‌دهند. ما Claims را برای نام، ایمیل، تاریخ تولد و برای یک شناسه منحصربه‌فرد مرتبط به JWT ایجاد کرده‌ایم.
این آرایه Claims به سازنده JwtSecurityToken پاس داده می‌شود تا در JWT ارسال شده به کلاینت درج شود.

حالا بیایید نگاهی به نحوه تغییر کد API بیندازیم تا هنگام بازگشت لیست کتاب‌ها سن کاربر را بگیریم:

[Route("api/[controller]")]
public class BooksController : Controller
{
  [HttpGet, Authorize]
  public IEnumerable<Book> Get()
  {
    var currentUser = HttpContext.User;
    int userAge = 0;
    var resultBookList = new Book[] {
      new Book { Author = "Ray Bradbury", Title = "Fahrenheit 451", AgeRestriction = false },
      new Book { Author = "Gabriel García Márquez", Title = "One Hundred years of Solitude", AgeRestriction = false },
      new Book { Author = "George Orwell", Title = "1984", AgeRestriction = false },
      new Book { Author = "Anais Nin", Title = "Delta of Venus", AgeRestriction = true }
    };

    if (currentUser.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
    {
      DateTime birthDate = DateTime.Parse(currentUser.Claims.FirstOrDefault(c => c.Type == ClaimTypes.DateOfBirth).Value);
      userAge = DateTime.Today.Year - birthDate.Year;
    }

    if (userAge < 18)
    {
      resultBookList = resultBookList.Where(b => !b.AgeRestriction).ToArray();
    }

    return resultBookList;
  }

  public class Book
  {
    public string Author { get; set; }
    public string Title { get; set; }
    public bool AgeRestriction { get; set; }
  }
}

ما ویژگی AgeRestriction را به کلاس Book اضافه کردیم. این یک مقدار boolean است که نشان می‌دهد آیا کتاب در آن رده سنی قرار دارد یا نه.

وقتی یک درخواست دریافت می شود، بررسی می‌کنیم آیا claim ی DateOfBirth مربوط به کاربر جاری است. در صورت مثبت بودن، سن کاربر را محاسبه می کنیم. سپس اگر کاربر زیر 18 سال باشد، لیست فقط شامل کتاب‌های بدون محدودیت سنی خواهد بود، در غیر این صورت کل لیست بازگشت داده می‌شود.

می‌توانید این سناریوی جدید را با اجرای تست‌های GetBooksWithoutAgeRestrictions و GetBooksWithAgeRestrictions در سورس کد پروژه یا با استفاده از دستور curl زیر امتحان کنید:

# signing in
curl -X POST -H 'Content-Type: application/json' -d '{username: "mary", password: "barbie"}' 0:5000/api/token

# setting JWT variable (replace AAA.BBB.CCC with token received)
JWT="AAA.BBB.CCC"

# getting books
curl -H 'Authorization: Bearer '$JWT 0:5000/api/books

آخرین دستور لیستی از همه کتاب‌ها بجز Delta of Venus را ارسال می‌کند.

فعال کردن درخواست Cross-Origin (CORS) در ASP.NET Core 2.0

هنگام ارسال درخواست AJAX، مرورگرها preflightها را ایجاد می‌کنند تا بررسی کنند که آیا سرور درخواست‌ها را از هاست دامین برنامه وب پذیرفته است. اگر پاسخ برای preflights شامل حداقل هدر Access-Control-Allow-Origin نباشد مشخص می شود که درخواست‌های مربوط به دامین اصلی را پذیرفته است، مرورگرها با درخواست واقعی (برای بهبود امنیت) ادامه نمی‌دهند.

برای پشتیبانی از CORS، باید دو تغییر در کلاس Startup ایجاد کنیم. ابتدا باید دستور زیر را اضافه کنیم:

services.AddCors(options =>
{
  options.AddPolicy("CorsPolicy",
      builder => builder.AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials()
  .Build());
});

به عنوان آخرین فراخوان در متد ConfigureServices. دوم، باید دستور زیر را اضافه کنیم:

app.UseCors("CorsPolicy");

توجه داشته باشید که این امر اساسا درخواست‌های پذیرفته شده API ما را از هر مبدأیی می‌پذیرد. برای ایجاد امنیت بیشتر می‌توانیم AllowAnyOrigin را با WithOrigins تغییر داده و یک مبدأ خاص را تعریف کنیم (مثلا  https://mydomain.com).

امنیت ASP.NET Core 2.0 با Auth0

امنیت برنامه های ASP.NET Core 2.0 با Auth0 آسان است و ویژگی‌های خوب بسیاری را همراه دارد. با Auth0، فقط باید چند خط کد بنویسیم تا مدیریت قدرتمند شناسایی (identitysingle sign-on (کنترل دسترسی چندین سیستم وابسته و در عین حال مستقل)، پشتیبانی از providerهای شناسایی قدرتمند (مثل فیس‌بوک، گیت‌هاب، توییتر و غیره) و پشتیبانی از providerهای شناسایی سازمانی (مثل Active Directory، LDAP، SAML و غیره) را به دست آوریم.

با ASP.NET Core 2.0،‌ ما فقط نیاز به ایجاد یک API در Auth0 Management Dashboard و تغییر دو چیز در کدمان داریم. برای ایجاد API، باید برای یک اکانت رایگان Auth0 ثبت نام کنیم. بعد از آن باید به قسمت API داشبورد برویم و روی " Create API" کلیک کنیم. در تصویر نشان داده شده است می‌توانیم نام API را مثلا "Books" ، Identifier را "http://books.mycompany.com" تنظیم کنیم و Signing Algorithm را " RS256" قرار دهیم.

بعد از آن باید فراخوانی برای services.AddAuthentication در Startup را به صورت زیر جایگزین کنیم:

string domain = $"https://{Configuration["Auth0:Domain"]}/";
services.AddAuthentication(options =>
{
  options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
  options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
  options.Authority = domain;
  options.Audience = Configuration["Auth0:Audience"];
});

و عنصر زیر را به appsettings.json اضافه کنیم:

{
  "Logging": {
    // ...
  },
  "Auth0": {
    "Domain": "bk-samples.auth0.com",
    "Audience": "http://books.mycompany.com"
  }
}

توجه داشته باشید که دامنه در این مورد باید به دامنه‌ای که ما هنگام ایجاد اکانت Auth0 خود مشخص کرده‌ایم تغییر کند.

تست یکپارچگی

این تمام چیزی است که باید ASP.NET Core 2.0 API خود را با Auth0 امن کنیم. با این حال، برای تست این یکپارچگی ما نیاز به یک کلاینت برای ارتباط با برنامه داریم. همانطور که تمرکز این مقاله بر روی ASP.NET Core 2.0 است، ما از یک برنامه وب عمومی استفاده خواهیم کرد که با یک برنامه قابل تنظیم Auth0 امن شده است. ما باید ویژگی‌های clientID، domain و audience را تنظیم کنیم.

برای دریافت ویژگی‌های clientID و domain، باید یک برنامه جدید در مدیریت داشبور ایجاد کنیم. در بخش Applications می‌توانیم روی "Create Application" کلیک کرده، آن را "Book Application" نامگذاری کرده و نوع آن را "Single Page Web Applications" انتخاب کنیم. بعد از ایجاد برنامه باید به تب "Settings" برویم و در فیلد "Allowed Callback URLs" آدرس http://auth0.digituz.com.br را اضافه کنیم و "Save" را بزنیم (ctrl/command + s). در این تب می‌توانیم هر دو ویژگی را که می‌خواهیم واکشی کنیم (Client ID و Domain) و سپس به برنامه عمومی اضافه کنیم. همچنین اینجا می‌توانیم audience را برای identifierی API مان تنظیم کنیم (مثل http://books.mycompany.com). حالا می‌توانیم "Sign In with Auth0" را برای تعیین اعتبارسنجی خودمان کلیک کنیم.

بعد از اعتبارسنجی می‌توانیم از برنامه وب برای درخواست‌های API (مثل http://localhost:5000/api/books) استفاده کنیم. همانطور که این برنامه وب دسترسی توکن ((access_token تولید شده را در فرآیند اعتبارسنجی در هدر Authorization اضافه می‌کند، API ما اعتبار آن را بررسی می‌کند و لیست کتاب‌ها را برای ما ارسال می‌کند.

نتیجه‌گیری

در این مقاله ما یک مرور کلی از تکنولوژی JSON Web Token را ارائه کرده و نحوه استفاده آن در ASP.NET Core 2 را معرفی کردیم. در حالی که یک برنامه Web API ساده را توسعه دادیم، شاهد شیوه پیکربندی پشتیبانی از اعتبارسنجی JWT و نحوه ایجاد توکن‌ها روی اعتبارسنجی بودیم. همچنین نحوه قرار دادن claims در JWT و نحوه استفاده از آن‌ها برای مجوز دسترسی به یک منبع را شرح دادیم.

در نتیجه، مدیریت آسان JSON Web Tokenها در برنامه‌های ASP.NET Core 2 را تجربه کردیم.