Token Authentication ( احراز هویت نشانه ای ) برای چند سال گذشته عنوانی مشهور بوده است، به خصوص اینکه برنامه های موبایل و JavaScript شهرت پیدا کردن در میان مشتریان را ادامه داده اند.

پذیرش و بکارگیری گسترده ی استانداردهای مبتنی بر نشانه مانند OAuth 2.0 و OpenID Connect نشانه ها را به تعداد بیشتری از توسعه دهندگان معرفی کرده است، اما بهترین روش ها همیشه واضح نیستند.

ASP.NET Core، به واسطه ی میان افزار اعتبارسنجی JWT داخلی، پشتیبانی عالی برای مصرف و اعتبارسنجی نشانه ای دارد.

با این حال، بسیاری افراد از حذف کد تولید نشانه ای از ASP.NET 4 متعجب بودند. در روزهای آغازین ASP.NET Core، داستان کامل Token Authentication ، موضوعی بهم ریخته و گیج کننده بود.

اکنون که ASP.NET Core 2.0 (بزودی ۲.۱) پایدار است، اوضاع آرام گرفته است. در این پست، بهترین روش ها برای هر دو طرف داستان Token Authentication ( احراز هویت نشانه ای ) را بررسی خواهم کرد: اعتبارسنجی نشانه ای و تولید نشانه ای.

Token Authentication چیست؟

Token Authentication ( احراز هویت نشانه ای ) ، فرآیند الحاق یک نشانه (گاهی معروف به یک نشانه ی دسترسی یا یک نشانه ی حامل) به درخواست های HTTP جهت احراز هویت آن هاست. بطور معمول با API هایی که به سرویس گیرنده های موبایل یا SPA (جاوا اسکریپت) سرویس می دهند، استفاده می شود.
هر درخواستی که به API می رسد بررسی می شود. اگر نشانه ای معتبر یافت شود، درخواست مجاز می شود.

اگر هیچ نشانه ای یافت نشود، یا نشانه نامعتبر باشد، درخواست با یک پاسخ ۴۰۱ Unauthorized (تأیید نشده) رد می شود.
Token Authentication معمولا در متن/محتوای OAuth 2.0 یا OpenID Connect مورد استفاده قرار می گیرد.

اعتبارسنجی نشانه ها در ASP.NET Core

افزودن Token Authentication به API خود در ASP.NET Core به واسطه ی میان افزار JwtBearerAuthentication گنجانده شده در فریمورک، آسان است.

اگر نشانه های ایجاد شده توسط یک سرور استاندارد OpenID Connect را مصرف می کنید، پیکربندی فوق العاده آسان است.

در کلاس Startup خود، میان افزار را هرجایی در متد ConfigureServices خود اضافه کرده، و آن را با مقادیری از سرور تأیید خود پیکربندی کنید:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.Authority = "{yourAuthorizationServerAddress}";
    options.Audience = "{yourAudience}";
});

سپس، در متد Configure خود، این خط را درست بالای UseMvc اضافه کنید:

app.UseAuthentication();

این مرحله ی دوم افزودن UseAuthentication() به آسانی فراموش می شود! این کار را چند مرتبه انجام داده ام.

اگر فراخوانی های احراز شده ی شما به درستی کار نمی کنند، مطمئن شوید که این خط را در جای درست اضافه کرده اید (بالای UseMvc).

میان افزار JwtBearer به دنبال نشانه ها (JSON Web Tokenها یا JWTها) در هدر HTTP Authorization درخواست های ورودی می گردد.

اگر نشانه ای معتبر یافت شد، درخواست تأیید می شود. سپس صفت [Authorize] را در کنترل کننده های خود یا مسیرهایی که می خواهید حفاظت شده باشند اضافه کنید:

[Route("/api/protected")
[Authorize]
public string Protected()
{
    return "Only if you have a valid token!";
}

ممکن است فکر کنید: تنها با مشخص شدن مراجع و مخاطبان، چگونه میان افزار JwtBearer، نشانه های ورودی را اعتبارسنجی می کند؟

فراداده ها (metadata) سرور تأیید خودکار

هنگامیکه میان افزار JwtBearer درخواستی را برای نخستین بار مدیریت می کند، تلاش می کند برخی metadata ها را از سرور تأیید (همچنین معروف به یک مرجع یا صادرکننده) بازیابی کند.

این metadata ها، یا سند اکتشاف در اصطلاحات OpenID Connect، حاوی کلیدهای عمومی و دیگر جزئیات مورد نیاز برای اعتبارسنجی نشانه ها است. (کنجکاوید که metadata ها چگونه اند؟ در اینجا یک سند اکتشاف نمونه آمده است.)

اگر میان افزار JwtBearer این سند metadata ها را بیابد، خود را بصورت خودکار پیکربندی می کند. بسیار زیرکانه!

اگر سند وجود نداشته باشد، خطایی دریافت خواهید کرد:

System.IO.IOException: IDX10804: Unable to retrieve document from: "{yourAuthorizationServerAddress}".
System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).

اگر سرور تأیید شما این metadata ها را منتشر نکند، یا تنها بخواهید خودتان پارامترهای اعتبارسنجی نشانه را مشخص کنید، می توانید آن ها را بصورت دستی به پیکربندی میان افزار اضافه کنید.

تعیین پارامترهای اعتبارسنجی نشانه

مجموعه ی کامل امکانات JwtBearer، در صورتیکه بخواهید کنترل fine-grained بر روی نحوه ی اعتبارسنجی نشانه های خود داشته باشید، می توانند مورد استفاده قرار بگیرند:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // Clock skew compensates for server time drift.
        // We recommend 5 minutes or less:
        ClockSkew = TimeSpan.FromMinutes(5),
        // Specify the key used to sign the token:
        IssuerSigningKey = signingKey,
        RequireSignedTokens = true,
        // Ensure the token hasn't expired:
        RequireExpirationTime = true,
        ValidateLifetime = true,
        // Ensure the token audience matches our audience value (default true):
        ValidateAudience = true,
        ValidAudience = "api://default",
        // Ensure the token was issued by a trusted authorization server (default true):
        ValidateIssuer = true,
        ValidIssuer = "https://{yourOktaDomain}/oauth2/default"
    };
});

رایج ترین امکانات جهت تنظیم در TokenValidationParameters، صادر کننده، مخاطبان، و انحراف پالس ساعت (clock skew) هستند.

همچنین نیاز خواهد بود کلید(ها)یی که نشانه هایتان با آن ها علامت گذاری می شوند را فراهم کنید، که بسته به اینکه از یک کلید متقارن یا نامتقارن استفاده کنید متفاوت بنظر خواهد رسید.

درک علامت گذاری متقارن و نامتقارن

نشانه های تولید شده توسط سرور تأییدتان یا با کلیدی متقارن (HS256) یا کلیدی نامتقارن (RS256) علامت گذاری خواهند شد.

اگر سرور تأییدتان یک سند اکتشاف منتشر کند، حاوی اطلاعات کلید خواهد بود، پس نیازی نیست در رابطه با نحوه ی کار آن نگران باشید.
با این حال، اگر خودتان میان افزار را پیکربندی می کنید یا نشانه ها را بصورت دستی اعتبارسنجی می کنید، باید نحوه ی علامت گذاری نشانه ها را درک کنید.

چه تفاوتی میان کلیدهای متقارن و نامتقارن وجود دارد؟

کلیدهای متقارن

یک کلید متقارن، همچنین معروف به کلید اشتراکی یا رمز اشتراکی، یک مقدار نهفته (مانند رمز عبور) است که هم در API (برنامه تان) و هم در سرور تأییدی که نشانه ها را صادر می کند، نگه داشته می شود.

سرور تأیید، بسته ی اطلاعاتی (payload) نشانه ای را با کلید اشتراکی علامت گذاری می کند، و API نشانه های ورودی را اعتبارسنجی می کند تا با استفاده از کلید یکسانی به درستی علامت گذاری شده باشند.

اگر یک کلید متقارن اشتراکی داشته باشید، استفاده از آن با میان افزار JwtBearer آسان است:

// For example only! Don't store your shared keys as strings in code.
// Use environment variables or the .NET Secret Manager instead.
var sharedKey = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes("mysupers3cr3tsharedkey!"));

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // Specify the key used to sign the token:
        IssuerSigningKey = sharedKey,
        RequireSignedTokens = true,
        // Other options...
    };
});

مطمئن شوید که کلید را امن نگه می دارید! ذخیره ی آن در کد خود (مانند مثال بالا) ایده ی بدی است، از آنجاییکه ورود و بررسی آن در کنترل منبع بطور تصادفی آسان است.

در عوض، آن را در متغیرهای محیطی در سرور خود ذخیره کنید، یا از .NET Secret Manager استفاده کنید.

الگوی پیکربندی ASP.NET Core بارگذاری مقدار از محیط یا رمزهای کاربر را آسان می کند:

var sharedKey = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(Configuration["SigningKey"]);

همچنین، کلید اشتراکی خود را در کد سمت کاربر (frontend) خود ذخیره نکرده یا در معرض مرورگر قرار ندهید. باید در سرورتان تحت حفاظت قرار بگیرد.

کلیدهای نامتقارن

با وجود علامت گذاری نامتقارن، نیازی نیست یک کلید نهان را در سرور خود نگه دارید.

در عوض، یک جفت کلید (keypair) عمومی/خصوصی استفاده می شود: سرور تأیید نشانه ها را با یک کلید نهان خصوصی علامت گذاری کرده، و یک کلید عمومی که فرد می تواند برای اعتبارسنجی نشانه ها از آن استفاده کند را منتشر می کند.

بطور معمول، اطلاعات کلید عمومی بصورت خودکار از سند اکتشاف، همانطور که در بخش فوق توضیح داده شد، بازیابی می شود.

اگر نیاز است آن را بصورت دستی مشخص کنید، نیاز خواهد بود که پارامترهای کلید را از سور تأیید گرفته و یک شیء SecurityKey ایجاد کنید:

// Manually specify a public (asymmetric) key published as a JWK:
var publicJwk = new JsonWebKey
{
    KeyId = "(some key ID)",
    Alg = "RS256",
    E = "AQAB",
    N = "(a long string)",
    Kty = "RSA",
    Use = "sig"
};

در اکثر موارد، کلیدهای عمومی در یک  JSON Web Key Set )JWKS)  (مجموعه کلید وب JSON) در سرور تأیید موجود هستند (در اینجا یک JWKS نمونه آورده شده است).

سرور تأیید ممکن است کلیدها را بصورت دوره ای نیز تغییر و دوران دهد، بنابراین نیاز خواهد بود بطور مرتب کلیدهای بروز شده را بررسی کنید.

اگر به میان افزار JwtBearer اجازه دهید که از طریق سند اکتشاف بصورت خودکار پیکربندی انجام دهد، تمام این ها بصورت خودکار انجام خواهند شد!

اعتبارسنجی نشانه ها بصورت دستی در ASP.NET Core

در برخی موارد، ممکن است نیاز باشد نشانه ها را بدون استفاده از میان افزار JwtBearer اعتبارسنجی کنید.

استفاده از میان افزار همیشه باید انتخاب اول باشد، از آنجاییکه به خوبی (و بصورت خودکار) به سیستم تأیید ASP.NET Core متصل می شود.

اگر بطور قطع نیاز به اعتبارسنجی یک JWT بصورت دستی دارید، می توانید از JwtSecurityTokenHandler در پکیج System.IdentityModel.Tokens.Jwt استفاده کنید.

از کلاس TokenValidationParameters یکسانی برای تعیین موارد اعتبارسنجی استفاده می کند:

private static JwtSecurityToken ValidateAndDecode(string jwt, IEnumerable<SecurityKey> signingKeys)
{
    var validationParameters = new TokenValidationParameters
    {
        // Clock skew compensates for server time drift.
        // We recommend 5 minutes or less:
        ClockSkew = TimeSpan.FromMinutes(5),
        // Specify the key used to sign the token:
        IssuerSigningKeys = signingKeys,
        RequireSignedTokens = true,
        // Ensure the token hasn't expired:
        RequireExpirationTime = true,
        ValidateLifetime = true,
        // Ensure the token audience matches our audience value (default true):
        ValidateAudience = true,
        ValidAudience = "api://default",
        // Ensure the token was issued by a trusted authorization server (default true):
        ValidateIssuer = true,
        ValidIssuer = "https://{yourOktaDomain}/oauth2/default"
    };

    try
    {
        var claimsPrincipal = new JwtSecurityTokenHandler()
            .ValidateToken(jwt, validationParameters, out var rawValidatedToken);

        return (JwtSecurityToken)rawValidatedToken;
        // Or, you can return the ClaimsPrincipal
        // (which has the JWT properties automatically mapped to .NET claims)
    }
    catch (SecurityTokenValidationException stvex)
    {
        // The token failed validation!
        // TODO: Log it or display an error.
        throw new Exception($"Token failed validation: {stvex.Message}");
    }
    catch (ArgumentException argex)
    {
        // The token was not well-formed or was invalid for some other reason.
        // TODO: Log it or display an error.
        throw new Exception($"Token was invalid: {argex.Message}");
    }
}

اگر سرور تأیید شما یک سند metadata منتشر کند، می توانید آن را با کلاس OpenIdConnectConfigurationRetriever در پکیج Microsoft.IdentityModel.Protocols.OpenIdConnect بازیابی کنید.

این کار به شما اجازه می دهد تا کلیدهای علامت گذاری را بصورت خودکار دریافت کنید:

var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
    // .well-known/oauth-authorization-server or .well-known/openid-configuration
    "{yourAuthorizationServerAddress}/.well-known/openid-configuration",
    new OpenIdConnectConfigurationRetriever(),
    new HttpDocumentRetriever());

var discoveryDocument = await configurationManager.GetConfigurationAsync();
var signingKeys = discoveryDocument.SigningKeys;

آن امکان طرف اعتبارسنجی تأیید نشانه ها را انجام می دهد، اما تولید خود نشانه ها چه؟

تولید نشانه ها جهت تأیید در ASP.NET Core

پیش تر در روزهای ASP.NET 4.5 میان افزار UseOAuthAuthorization نقظه ای پایانی به شما می داد که به راحتی می توانست نشانه هایی را برای برنامه تان تولید کند.

با این حال، تیم ASP.NET Core تصمیم گرفتند آن امکان را به ASP.NET Core وارد نکنند، بدین معنا که نیاز خواهد بود مورد دیگری را متصل کنید.

بطور خاص، نیاز خواهد بود یک سرور تأیید را یا یافته یا بسازید که بتواند نشانه ها را تولید کند.

دو روش رایج برای دستیابی به یک سرور تأیید این ها هستند:

  •  استفاده از یک سرویس ابری مانند Azure AD B2C یا Okta
  • ساخت یا پیکربندی سرور تأیید خودتان

سرور تأیید میزبانی شده با Okta

یک سرور تأیید میزبانی شده آسان ترین راه برای تولید نشانه هاست، چراکه نیازی نیست خودتان چیزی بسازید (یا نگهداری /کنترل کنید).

می توانید برای یک حساب رایگان ثبت نام کرده و سپس شروع سریع Okta + ASP.NET Core API را برای دستورالعمل های مرحله به مرحله دنبال کنید.

از آنجاییکه تولیدات سرور تأیید Okta برای شما سند اکتشاف استانداردی دارد، پیکربندی JwtBearer فوق العاده ساده است:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.Authority = "https://{yourOktaDomain}/oauth2/default";
    options.Audience = "api://default";
});

اگر می خواهید سرور تأیید خود را بگردانید، می توانید یکی از پکیج های ساخت جامعه مشهور را استفاده کنید:

OpenIddict

OpenIddict یک سرور تأیید با پیکربندی آسان است که به خوبی با ASP.NET Core Identity و Entity Framework Core کار می کند.

مستقیما به خط لوله ی میان افزار ASP.NET Core متصل شده و پیکربندی آن آسان است.

OpenIddict انتخابی عالی است اگر از قبل از ASP.NET Core Identity استفاده می کنید و می خواهید نشانه هایی را برای کاربران خود تولید کنید.

می توانید آموزش عمقی و کامل Mike Rousos در بلاگ MSDN برای راه اندازی و پیکربندی آن در برنامه تان دنبال کنید.

ASOS

پکیج AspNet.Security.OpenIdConnect.Server در سطح پایین تری نسبت به OpenIddict قرار دارد (در حقیقت، OpenIddict در باطن از آن استفاده می کند).

راه اندازی آن کار بیشتری می برد، اما زمانی مفید است که می خواهید کنترل مستقیم بیشتری بر روی نحوه ی مدیریت پروتکل OpenID Connect و نحوه ی تولید نشانه ها داشته باشید.

Kévin Chalet آموزشی عمقی و کامل در رابطه با ایجاد یک سرور OpenID Connect در بلاگ خود دارد.

IdentityServer4

پروژه ی متن باز IdentityServer مربوط به Thinktecture مدت زیادی وجود داشته است، و بروزرسانی اصلی و عمده ای برای .NET Core با IdentityServer4 دریافت کرد.

از بین سه پکیج مورد بحث در اینجا، قدرتمندترین و انعطاف پذیرترین است.

IdentityServer انتخاب خوبی است زمانیکه بخواهید سرور تأیید OpenID Connect تمام و کمال خود را که بتواند موارد کاربری پیچیده مانند federation (فدراسیون) و single sign-on (شناسایی یگانه/ورود یکپارچه) را مدیریت کند، بگردانید.

بسته به مورد کاربری شما، پیکربندی IdentityServer4 می تواند کمی پیچیده باشد.

خوشبختانه، اسناد رسمی بسیاری از سناریوهای رایج را پوشش می دهد.

Token Authentication می تواند پیچیده باشد!

امیدوارم این مقاله به کاهش پیچیدگی و گیج کنندگی آن کمک کند.

تیم ASP.NET Core کاری عالی در جهت آسان کردن افزودنToken Authentication به ASP.NET Core API شما انجام داده اند، و امکاناتی مانند OpenIddict و Okta راه اندازی مجدد یک سرور تأیید که نشانه هایی را برای سرویس گیرندگان شما تولید می کند، را آسان می کند.

زهره سلطانیان

نوشته‌های مرتبط

دیدگاه‌ها

*
*

این سایت از اکیسمت برای کاهش هرزنامه استفاده می کند. بیاموزید که چگونه اطلاعات دیدگاه های شما پردازش می‌شوند.