اشتراک‌ها
font-face ایجاد کننده

 The @font-face CSS at-rule allows authors to specify online fonts to display text on their web pages. By allowing authors to provide their own fonts, @font-face eliminates the need to depend on the limited number of fonts users have installed on their computers. Unfortunately different browsers understand differnt font types. This service allows you to generate css rule and font files that makes your font work in all the browsers that support font-face or webfonts. 

font-face ایجاد کننده
اشتراک‌ها
شناخت NET Core, NETStandard, .NET Core applications and ASP.NET Core

As anyone in the .NET community who hasn't been living under a rock will know, there's a lot of exciting things happening with .NET at the moment with the announcement of the open source, cross platform, .NET Core. However, partly due to the very open nature of its evolution, there's been a whole host of names associated with its development - vNext, ASP.NET 5, ASP.NET Core, .NET generations etc.

In this post I'm going to try and clarify some of the naming and terminology surrounding the evolution of the .NET framework. I'll discuss some of the challenges the latest iteration is attempting to deal with and how the latest developments aim to address these.

This is really for those that have seen some of the big announcements but aren't sure about the intricacies of this new framework and how it relates to the existing ecosystem, which was my situation before I really started digging into it all properly!

Hopefully by the end of this article you'll have a clearer grasp of the latest in .NET! 

شناخت NET Core, NETStandard, .NET Core applications and ASP.NET Core
اشتراک‌ها
ده گام برای امنیت نرم افزار

OWASP’s Top 10 Risk List is an important tool for security engineers and compliance analysts. It describes the 10 worst security problems that are found in web and mobile applications today. But, on its own, it’s not much help to developers, so OWASP has come up with a list of 10 things that you can do as a developer to make sure that your code is safe and secure. 

ده گام برای امنیت نرم افزار
اشتراک‌ها
Flux چیست ؟

Flux is an architecture for creating data layers in JavaScript applications. It was designed at Facebook along with the React view library. It places a focus on creating explicit and understandable update paths for your application's data, which makes tracing changes during development simpler and makes bugs easier to track down and fix. 

Flux چیست ؟
اشتراک‌ها
نگارش نهایی ASP.NET Core 2.2 منتشر شد

The main theme for this ASP.NET Core release was to improve developer productivity and platform functionality with regard to building Web/HTTP APIs. As usual, we made some performance improvements as well.  

نگارش نهایی ASP.NET Core 2.2 منتشر شد
مطالب
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
در قسمت قبل، امکان سفارش یک اتاق را به همراه پرداخت آنلاین آن، به برنامه‌ی Blazor WASM این سری اضافه کردیم؛ اما ... هویت کاربری که مشغول انجام اینکار است، هنوز مشخص نیست. بنابراین در این قسمت می‌خواهیم مباحثی مانند ثبت نام و ورود به سیستم را تکمیل کنیم. البته مقدمات سمت سرور این بحث را در مطلب «Blazor 5x - قسمت 25 - تهیه API مخصوص Blazor WASM - بخش 2 - تامین پایه‌ی اعتبارسنجی و احراز هویت»، بررسی کردیم.


ارائه‌ی AuthenticationState به تمام کامپوننت‌های یک برنامه‌ی Blazor WASM

در قسمت 22، با مفاهیم CascadingAuthenticationState و AuthorizeRouteView در برنامه‌های Blazor Server آشنا شدیم؛ این مفاهیم در اینجا نیز یکی هستند:
- کامپوننت CascadingAuthenticationState سبب می‌شود AuthenticationState (لیستی از Claims کاربر)، به تمام کامپوننت‌های یک برنامه‌یBlazor  ارسال شود. در مورد پارامترهای آبشاری، در قسمت نهم این سری بیشتر بحث شد و هدف از آن، ارائه‌ی یکسری اطلاعات، به تمام زیر کامپوننت‌های یک کامپوننت والد است؛ بدون اینکه نیاز باشد مدام این پارامترها را در هر زیر کامپوننتی، تعریف و تنظیم کنیم. همینقدر که آن‌ها را در بالاترین سطح سلسله مراتب کامپوننت‌های تعریف شده تعریف کردیم، در تمام زیر کامپوننت‌های آن نیز در دسترس خواهند بود.
- کامپوننت AuthorizeRouteView امکان محدود کردن دسترسی به صفحات مختلف برنامه‌ی Blazor را بر اساس وضعیت اعتبارسنجی و نقش‌های کاربر جاری، میسر می‌کند.

روش اعمال این دو کامپوننت نیز یکی است و نیاز به ویرایش فایل BlazorWasm.Client\App.razor در اینجا وجود دارد:
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <Authorizing>
                    <p>Please wait, we are authorizing the user.</p>
                </Authorizing>
                <NotAuthorized>
                    <p>Not Authorized</p>
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
                <LayoutView Layout="@typeof(MainLayout)">
                    <p>Sorry, there's nothing at this address.</p>
                </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
کامپوننت CascadingAuthenticationState، اطلاعات AuthenticationState را در اختیار تمام کامپوننت‌های برنامه قرار می‌دهد و کامپوننت AuthorizeRouteView، امکان نمایش یا عدم نمایش قسمتی از صفحه را بر اساس وضعیت لاگین شخص و یا محدود کردن دسترسی بر اساس نقش‌ها، میسر می‌کند.


مشکل! برخلاف برنامه‌های Blazor Server، برنامه‌های Blazor WASM به صورت پیش‌فرض به همراه تامین کننده‌ی توکار AuthenticationState نیستند.

اگر سری Blazor جاری را از ابتدا دنبال کرده باشید، کاربرد AuthenticationState را در برنامه‌های Blazor Server، در قسمت‌های 21 تا 23، پیشتر مشاهده کرده‌اید. همان مفاهیم، در برنامه‌های Blazor WASM هم قابل استفاده هستند؛ البته در اینجا به علت جدا بودن برنامه‌ی سمت کلاینت WASM Blazor، از برنامه‌ی Web API سمت سرور، نیاز است یک تامین کننده‌ی سمت کلاینت AuthenticationState را بر اساس JSON Web Token دریافتی از سرور، تشکیل دهیم و برخلاف برنامه‌های Blazor Server، این مورد به صورت خودکار مدیریت نمی‌شود و با ASP.NET Core Identity سمت سروری که JWT تولید می‌کند، یکپارچه نیست.
بنابراین در اینجا نیاز است یک AuthenticationStateProvider سفارشی سمت کلاینت را تهیه کنیم که بر اساس JWT دریافتی از Web API کار می‌کند. به همین جهت در ابتدا یک JWT Parser را طراحی می‌کنیم که رشته‌ی JWT دریافتی از سرور را تبدیل به <IEnumerable<Claim می‌کند. سپس این لیست را در اختیار یک AuthenticationStateProvider سفارشی قرار می‌دهیم تا اطلاعات مورد نیاز کامپوننت‌های CascadingAuthenticationState و AuthorizeRouteView تامین شده و قابل استفاده شوند.


نیاز به یک JWT Parser

در قسمت 25، پس از لاگین موفق، یک JWT تولید می‌شود که به همراه قسمتی از مشخصات کاربر است. می‌توان محتوای این توکن را در سایت jwt.io مورد بررسی قرار داد که برای نمونه به این خروجی می‌رسیم و حاوی claims تعریف شده‌است:
{
  "iss": "https://localhost:5001/",
  "iat": 1616396383,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "vahid@dntips.ir",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "vahid@dntips.ir",
  "Id": "582855fb-e95b-45ab-b349-5e9f7de40c0c",
  "DisplayName": "vahid@dntips.ir",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin",
  "nbf": 1616396383,
  "exp": 1616397583,
  "aud": "Any"
}
بنابراین برای استخراج این claims در سمت کلاینت، نیاز به یک JWT Parser داریم که نمونه‌ای از آن می‌تواند به صورت زیر باشد:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;

namespace BlazorWasm.Client.Utils
{
    /// <summary>
    /// From the Steve Sanderson’s Mission Control project:
    /// https://github.com/SteveSandersonMS/presentation-2019-06-NDCOslo/blob/master/demos/MissionControl/MissionControl.Client/Util/ServiceExtensions.cs
    /// </summary>
    public static class JwtParser
    {
        public static IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
        {
            var claims = new List<Claim>();
            var payload = jwt.Split('.')[1];

            var jsonBytes = ParseBase64WithoutPadding(payload);

            var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
            claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
            return claims;
        }

        private static byte[] ParseBase64WithoutPadding(string base64)
        {
            switch (base64.Length % 4)
            {
                case 2: base64 += "=="; break;
                case 3: base64 += "="; break;
            }
            return Convert.FromBase64String(base64);
        }
    }
}
که آن‌را در فایل BlazorWasm.Client\Utils\JwtParser.cs برنامه‌ی کلاینت ذخیره خواهیم کرد. متد ParseClaimsFromJwt فوق، رشته‌ی JWT تولیدی حاصل از لاگین موفق در سمت Web API را دریافت کرده و تبدیل به لیستی از Claimها می‌کند.


تامین AuthenticationState مبتنی بر JWT مخصوص برنامه‌‌های Blazor WASM

پس از داشتن لیست Claims دریافتی از یک رشته‌ی JWT، اکنون می‌توان آن‌را تبدیل به یک AuthenticationStateProvider کرد. برای اینکار در ابتدا نیاز است بسته‌ی نیوگت Microsoft.AspNetCore.Components.Authorization را به برنامه‌ی کلاینت اضافه کرد:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="5.0.4" />
  </ItemGroup>
</Project>
سپس سرویس سفارشی AuthStateProvider خود را به پوشه‌ی Services برنامه اضافه می‌کنیم و متد GetAuthenticationStateAsync کلاس پایه‌ی AuthenticationStateProvider استاندارد را به نحو زیر بازنویسی و سفارشی سازی می‌کنیم:
namespace BlazorWasm.Client.Services
{
    public class AuthStateProvider : AuthenticationStateProvider
    {
        private readonly HttpClient _httpClient;
        private readonly ILocalStorageService _localStorage;

        public AuthStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
            _localStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage));
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var token = await _localStorage.GetItemAsync<string>(ConstantKeys.LocalToken);
            if (token == null)
            {
                return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
            }

            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
            return new AuthenticationState(
                        new ClaimsPrincipal(
                            new ClaimsIdentity(JwtParser.ParseClaimsFromJwt(token), "jwtAuthType")
                        )
                    );
        }
    }
}
- اگر با برنامه‌های سمت کلاینت React و یا Angular پیشتر کار کرده باشید، منطق این کلاس بسیار آشنا به نظر می‌رسد. در این برنامه‌ها، مفهومی به نام Interceptor وجود دارد که توسط آن به صورت خودکار، هدر JWT را به تمام درخواست‌های ارسالی به سمت سرور، اضافه می‌کنند تا از تکرار این قطعه کد خاص، جلوگیری شود. علت اینجا است که برای دسترسی به منابع محافظت شده‌ی سمت سرور، نیاز است هدر ویژه‌ای را به نام "Authorization" که با مقدار "bearer jwt" تشکیل می‌شود، به ازای هر درخواست ارسالی به سمت سرور نیز ارسال کرد؛ تا تنظیمات ویژه‌ی AddJwtBearer که در قسمت 25 در کلاس آغازین برنامه‌ی Web API انجام دادیم، این هدر مورد انتظار را دریافت کرده و پردازش کند و در نتیجه‌ی آن، شیء this.User، در اکشن متدهای کنترلرها تشکیل شده و قابل استفاده شود.
در اینجا نیز مقدار دهی خودکار httpClient.DefaultRequestHeaders.Authorization را مشاهده می‌کنید که مقدار token خودش را از Local Storage دریافت می‌کند که کلید متناظر با آن‌را در پروژه‌ی BlazorServer.Common به صورت زیر تعریف کرده‌ایم:
namespace BlazorServer.Common
{
    public static class ConstantKeys
    {
        // ...
        public const string LocalToken = "JWT Token";
    }
}
به این ترتیب دیگر نیازی نخواهد بود در تمام سرویس‌های برنامه‌ی WASM که با HttpClient کار می‌کنند، مدام سطر مقدار دهی httpClient.DefaultRequestHeaders.Authorization را تکرار کنیم.
- همچنین در اینجا به کمک متد JwtParser.ParseClaimsFromJwt که در ابتدای بحث تهیه کردیم، لیست Claims دریافتی از JWT ارسالی از سمت سرور را تبدیل به یک AuthenticationState قابل استفاده‌ی در برنامه‌ی Blazor WASM کرده‌ایم.

پس از تعریف یک AuthenticationStateProvider سفارشی، باید آن‌را به همراه Authorization، به سیستم تزریق وابستگی‌های برنامه در فایل Program.cs اضافه کرد:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ...

            builder.Services.AddAuthorizationCore();
            builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>();

            // ...
        }
    }
}
و برای سهولت استفاده‌ی از امکانات اعتبارسنجی فوق در کامپوننت‌های برنامه، فضای نام زیر را به فایل BlazorWasm.Client\_Imports.razor اضافه می‌کنیم:
@using Microsoft.AspNetCore.Components.Authorization


تهیه‌ی سرویسی برای کار با AccountController

اکنون می‌خواهیم در برنامه‌ی سمت کلاینت، از AccountController سمت سرور که آن‌را در قسمت 25 این سری تهیه کردیم، استفاده کنیم. بنابراین نیاز است سرویس زیر را تدارک دید که امکان لاگین، ثبت نام و خروج از سیستم را در سمت کلاینت میسر می‌کند:
namespace BlazorWasm.Client.Services
{
    public interface IClientAuthenticationService
    {
        Task<AuthenticationResponseDTO> LoginAsync(AuthenticationDTO userFromAuthentication);
        Task LogoutAsync();
        Task<RegisterationResponseDTO> RegisterUserAsync(UserRequestDTO userForRegisteration);
    }
}
و به صورت زیر پیاده سازی می‌شود:
namespace BlazorWasm.Client.Services
{
    public class ClientAuthenticationService : IClientAuthenticationService
    {
        private readonly HttpClient _client;
        private readonly ILocalStorageService _localStorage;

        public ClientAuthenticationService(HttpClient client, ILocalStorageService localStorage)
        {
            _client = client;
            _localStorage = localStorage;
        }

        public async Task<AuthenticationResponseDTO> LoginAsync(AuthenticationDTO userFromAuthentication)
        {
            var response = await _client.PostAsJsonAsync("api/account/signin", userFromAuthentication);
            var responseContent = await response.Content.ReadAsStringAsync();
            var result = JsonSerializer.Deserialize<AuthenticationResponseDTO>(responseContent);

            if (response.IsSuccessStatusCode)
            {
                await _localStorage.SetItemAsync(ConstantKeys.LocalToken, result.Token);
                await _localStorage.SetItemAsync(ConstantKeys.LocalUserDetails, result.UserDTO);
                _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.Token);
                return new AuthenticationResponseDTO { IsAuthSuccessful = true };
            }
            else
            {
                return result;
            }
        }

        public async Task LogoutAsync()
        {
            await _localStorage.RemoveItemAsync(ConstantKeys.LocalToken);
            await _localStorage.RemoveItemAsync(ConstantKeys.LocalUserDetails);
            _client.DefaultRequestHeaders.Authorization = null;
        }

        public async Task<RegisterationResponseDTO> RegisterUserAsync(UserRequestDTO userForRegisteration)
        {
            var response = await _client.PostAsJsonAsync("api/account/signup", userForRegisteration);
            var responseContent = await response.Content.ReadAsStringAsync();
            var result = JsonSerializer.Deserialize<RegisterationResponseDTO>(responseContent);

            if (response.IsSuccessStatusCode)
            {
                return new RegisterationResponseDTO { IsRegisterationSuccessful = true };
            }
            else
            {
                return result;
            }
        }
    }
}
که به نحو زیر به سیستم تزریق وابستگی‌های برنامه معرفی می‌شود:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ...
            builder.Services.AddScoped<IClientAuthenticationService, ClientAuthenticationService>();
            // ...
        }
    }
}
توضیحات:
- متد LoginAsync، مشخصات لاگین کاربر را به سمت اکشن متد api/account/signin ارسال کرده و در صورت موفقیت این عملیات، اصل توکن دریافتی را به همراه مشخصاتی از کاربر، در Local Storage ذخیره سازی می‌کند. این مورد سبب خواهد شد تا بتوان به مشخصات کاربر در صفحات دیگر و سرویس‌های دیگری مانند AuthStateProvider ای که تهیه کردیم، دسترسی پیدا کنیم. به علاوه مزیت دیگر کار با Local Storage، مواجه شدن با حالت‌هایی مانند Refresh کامل صفحه و برنامه، توسط کاربر است. در یک چنین حالتی، برنامه از نو بارگذاری مجدد می‌شود و به این ترتیب می‌توان به مشخصات کاربر لاگین کرده، به سادگی دسترسی یافت و مجددا قسمت‌های مختلف برنامه را به او نشان داد. نمونه‌ی دیگر این سناریو، بازگشت از درگاه پرداخت بانکی است. در این حالت نیز از یک سرویس سمت سرور دیگر، کاربر به سمت برنامه‌ی کلاینت، Redirect کامل خواهد شد که در اصل اتفاقی که رخ می‌دهد، با Refresh کامل صفحه یکی است. در این حالت نیز باید بتوان کاربری را که از درگاه بانکی ثالث، به سمت برنامه‌ی کلاینت از نو بارگذاری شده، هدایت شده، بلافاصله تشخیص داد.

- اگر برنامه، Refresh کامل نشود، نیازی به Local Storage نخواهد بود؛ از این لحاظ که در برنامه‌های سمت کلاینت Blazor، طول عمر تمام سرویس‌ها، صرفنظر از نوع طول عمری که برای آن‌ها مشخص می‌کنیم، همواره Singleton هستند (ماخذ).
Blazor WebAssembly apps don't currently have a concept of DI scopes. Scoped-registered services behave like Singleton services.
بنابراین می‌توان یک سرویس سراسری توکن را تهیه و به سادگی آن‌را در تمام قسمت‌های برنامه تزریق کرد. این روش هرچند کار می‌کند، اما همانطور که عنوان شد، به Refresh کامل صفحه حساس است. اگر برنامه در مرورگر کاربر Refresh نشود، تا زمانیکه باز است، سرویس‌های در اصل Singleton تعریف شده‌ی در آن نیز در تمام قسمت‌های برنامه در دسترس هستند؛ اما با Refresh کامل صفحه، به علت بارگذاری مجدد کل برنامه، سرویس‌های آن نیز از نو، وهله سازی خواهند شد که سبب از دست رفتن حالت قبلی آن‌ها می‌شود. بنابراین نیاز به روشی داریم که بتوانیم حالت قبلی برنامه را در زمان راه اندازی اولیه‌ی آن بازیابی کنیم و یکی از روش‌های استاندارد اینکار، استفاده از Local Storage خود مرورگر است که مستقل از برنامه و توسط مرورگر مدیریت می‌شود.

- در متد LoginAsync، علاوه بر ثبت اطلاعات کاربر در Local Storage، مقدار دهی client.DefaultRequestHeaders.Authorization را نیز ملاحظه می‌کنید. همانطور که عنوان شد، سرویس‌های Blazor WASM در اصل دارای طول عمر Singleton هستند. بنابراین تنظیم این هدر در اینجا، بر روی تمام سرویس‌های HttpClient تزریق شده‌ی به سایر سرویس‌های برنامه نیز بلافاصله تاثیرگذار خواهد بود.

- متد LogoutAsync، اطلاعاتی را که در حین لاگین موفق در Local Storage ذخیره کردیم، حذف کرده و همچنین client.DefaultRequestHeaders.Authorization را نیز نال می‌کند تا دیگر اطلاعات لاگین شخص قابل بازیابی نبوده و مورد استفاده قرار نگیرد. همین مقدار برای شکست پردازش درخواست‌های ارسالی به منابع محافظت شده‌ی سمت سرور کفایت می‌کند.

- متد RegisterUserAsync، مشخصات کاربر در حال ثبت نام را به سمت اکشن متد api/account/signup ارسال می‌کند که سبب افزوده شدن کاربر جدیدی به بانک اطلاعاتی برنامه و سیستم ASP.NET Core Identity خواهد شد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-31.zip
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت دوازدهم- یکپارچه سازی با اکانت گوگل
در مطلب قبلی «استفاده از تامین کننده‌های هویت خارجی»، نحوه‌ی استفاده از اکانت‌های ویندوزی کاربران یک شبکه، به عنوان یک تامین کننده‌ی هویت خارجی بررسی شد. در ادامه می‌خواهیم از اطلاعات اکانت گوگل و IDP مبتنی بر OAuth 2.0 آن به عنوان یک تامین کننده‌ی هویت خارجی دیگر استفاده کنیم.


ثبت یک برنامه‌ی جدید در گوگل

اگر بخواهیم از گوگل به عنوان یک IDP ثالث در IdentityServer استفاده کنیم، نیاز است در ابتدا برنامه‌ی IDP خود را به آن معرفی و در آنجا ثبت کنیم. برای این منظور مراحل زیر را طی خواهیم کرد:

1- مراجعه به developer console گوگل و ایجاد یک پروژه‌ی جدید
https://console.developers.google.com
در صفحه‌ی باز شده، بر روی دکمه‌ی select project در صفحه و یا لینک select a project در نوار ابزار آن کلیک کنید. در اینجا دکمه‌ی new project و یا create را مشاهده خواهید کرد. هر دوی این مفاهیم به صفحه‌ی زیر ختم می‌شوند:


در اینجا نامی دلخواه را وارد کرده و بر روی دکمه‌ی create کلیک کنید.

2- فعالسازی API بر روی این پروژه‌ی جدید


در ادامه بر روی لینک Enable APIs And Services کلیک کنید و سپس google+ api را جستجو نمائید.
پس از ظاهر شدن آن، این گزینه را انتخاب و در صفحه‌ی بعدی، آن‌را با کلیک بر روی دکمه‌ی enable، فعال کنید.

3- ایجاد credentials


در اینجا بر روی دکمه‌ی create credentials کلیک کرده و در صفحه‌ی بعدی، این سه گزینه را با مقادیر مشخص شده، تکمیل کنید:
• Which API are you using? – Google+ API
• Where will you be calling the API from? – Web server (e.g. node.js, Tomcat)
• What data will you be accessing? – User data
سپس در ذیل این صفحه بر روی دکمه‌ی «What credentials do I need» کلیک کنید تا به صفحه‌ی پس از آن هدایت شوید. اینجا است که مشخصات کلاینت OAuth 2.0 تکمیل می‌شوند. در این صفحه، سه گزینه‌ی آن‌را به صورت زیر تکمیل کنید:
• نام: همان مقدار پیش‌فرض آن
• Authorized JavaScript origins: آن‌را خالی بگذارید.
• Authorized redirect URIs: این مورد همان callback address مربوط به IDP ما است که در اینجا آن‌را با آدرس زیر مقدار دهی خواهیم کرد.
https://localhost:6001/signin-google
این آدرس، به آدرس IDP لوکال ما اشاره می‌کند و مسیر signin-google/ آن باید به همین نحو تنظیم شود تا توسط برنامه شناسایی شود.
سپس در ذیل این صفحه بر روی دکمه‌ی «Create OAuth 2.0 Client ID» کلیک کنید تا به صفحه‌ی «Set up the OAuth 2.0 consent screen» بعدی هدایت شوید. در اینجا دو گزینه‌ی آن‌را به صورت زیر تکمیل کنید:
- Email address: همان آدرس ایمیل واقعی شما است.
- Product name shown to users: یک نام دلخواه است. نام برنامه‌ی خود را برای نمونه ImageGallery وارد کنید.
برای ادامه بر روی دکمه‌ی Continue کلیک نمائید.

4- دریافت credentials
در پایان این گردش کاری، به صفحه‌ی نهایی «Download credentials» می‌رسیم. در اینجا بر روی دکمه‌ی download کلیک کنید تا ClientId  و ClientSecret خود را توسط فایلی به نام client_id.json دریافت نمائید.
سپس بر روی دکمه‌ی Done در ذیل صفحه کلیک کنید تا این پروسه خاتمه یابد.


تنظیم برنامه‌ی IDP برای استفاده‌ی از محتویات فایل client_id.json

پس از پایان عملیات ایجاد یک برنامه‌ی جدید در گوگل و فعالسازی Google+ API در آن، یک فایل client_id.json را دریافت می‌کنیم که اطلاعات آن باید به صورت زیر به فایل آغازین برنامه‌ی IDP اضافه شود:
الف) تکمیل فایل src\IDP\DNT.IDP\appsettings.json
{
  "Authentication": {
    "Google": {
      "ClientId": "xxxx",
      "ClientSecret": "xxxx"
    }
  }
}
در اینجا مقادیر خواص client_secret و client_id موجود در فایل client_id.json دریافت شده‌ی از گوگل را به صورت فوق به فایل appsettings.json اضافه می‌کنیم.
ب) تکمیل اطلاعات گوگل در کلاس آغازین برنامه
namespace DNT.IDP
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
   // ... 

            services.AddAuthentication()
                .AddGoogle(authenticationScheme: "Google", configureOptions: options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                    options.ClientId = Configuration["Authentication:Google:ClientId"];
                    options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
                });
        }
خود ASP.NET Core از تعریف اطلاعات اکانت‌های Google, Facebook, Twitter, Microsoft Account و  OpenID Connect پشتیبانی می‌کند که در اینجا نحوه‌ی تنظیم اکانت گوگل آن‌را مشاهده می‌کنید.
- authenticationScheme تنظیم شده باید یک عبارت منحصربفرد باشد.
- همچنین SignInScheme یک چنین مقداری را در اصل دارد:
 public const string ExternalCookieAuthenticationScheme = "idsrv.external";
از این نام برای تشکیل قسمتی از نام کوکی که اطلاعات اعتبارسنجی گوگل در آن ذخیره می‌شود، کمک گرفته خواهد شد.


آزمایش اعتبارسنجی کاربران توسط اکانت گوگل آن‌ها

اکنون که تنظیمات اکانت گوگل به پایان رسید و همچنین به برنامه نیز معرفی شد، برنامه‌ها را اجرا کنید. مشاهده خواهید کرد که امکان لاگین توسط اکانت گوگل نیز به صورت خودکار به صفحه‌ی لاگین IDP ما اضافه شده‌است:


در اینجا با کلیک بر روی دکمه‌ی گوگل، به صفحه‌ی لاگین آن که به همراه نام برنامه‌ی ما است و انتخاب اکانتی از آن هدایت می‌شویم:


پس از آن، از طرف گوگل به صورت خودکار به IDP (همان آدرسی که در فیلد Authorized redirect URIs وارد کردیم)، هدایت شده و callback رخ‌داده، ما را به سمت صفحه‌ی ثبت اطلاعات کاربر جدید هدایت می‌کند. این تنظیمات را در قسمت قبل ایجاد کردیم:
namespace DNT.IDP.Controllers.Account
{
    [SecurityHeaders]
    [AllowAnonymous]
    public class ExternalController : Controller
    {
        public async Task<IActionResult> Callback()
        {
            var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
            var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";

            var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
            if (user == null)
            {
                // user = AutoProvisionUser(provider, providerUserId, claims);
                
                var returnUrlAfterRegistration = Url.Action("Callback", new { returnUrl = returnUrl });
                var continueWithUrl = Url.Action("RegisterUser", "UserRegistration" ,
                    new { returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId });
                return Redirect(continueWithUrl);
            }
اگر خروجی متد FindUserFromExternalProvider آن null باشد، یعنی کاربری که از سمت تامین کننده‌ی هویت خارجی/گوگل به برنامه‌ی ما وارد شده‌است، دارای اکانتی در سمت IDP نیست. به همین جهت او را به سمت صفحه‌ی ثبت نام کاربر هدایت می‌کنیم.
در اینجا نحوه‌ی اصلاح اکشن متد Callback را جهت هدایت یک کاربر جدید به صفحه‌ی ثبت نام و تکمیل اطلاعات مورد نیاز IDP را مشاهده می‌کنید.
returnUrl ارسالی به اکشن متد RegisterUser، به همین اکشن متد جاری اشاره می‌کند. یعنی کاربر پس از تکمیل اطلاعات و اینبار نال نبودن user او، گردش کاری جاری را ادامه خواهد داد و به برنامه با این هویت جدید وارد می‌شود.


اتصال کاربر وارد شده‌ی از طریق یک IDP خارجی به اکانتی که هم اکنون در سطح IDP ما موجود است

تا اینجا اگر کاربری از طریق یک IDP خارجی به برنامه وارد شود، او را به صفحه‌ی ثبت نام کاربر هدایت کرده و پس از دریافت اطلاعات او، اکانت خارجی او را به اکانتی جدید که در IDP خود ایجاد می‌کنیم، متصل خواهیم کرد. به همین جهت بار دومی که این کاربر به همین ترتیب وارد سایت می‌شود، دیگر صفحه‌ی ثبت نام و تکمیل اطلاعات را مشاهده نمی‌کند. اما ممکن است کاربری که برای اولین بار از طریق یک IDP خارجی به سایت ما وارد شده‌است، هم اکنون دارای یک اکانت دیگری در سطح IDP ما باشد؛ در اینجا فقط اتصالی بین این دو صورت نگرفته‌است. بنابراین در این حالت بجای ایجاد یک اکانت جدید، بهتر است از همین اکانت موجود استفاده کرد و صرفا اتصال UserLogins او را تکمیل نمود.
به همین جهت ابتدا نیاز است لیست Claims بازگشتی از گوگل را بررسی کنیم:
var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);  
foreach (var claim in claims)
{
   _logger.LogInformation($"External provider[{provider}] info-> claim:{claim.Type}, value:{claim.Value}");
}
در اینجا پس از فراخوانی FindUserFromExternalProvider، لیست Claims بازگشت داده شده‌ی توسط IDP خارجی را لاگ می‌کنیم که در حالت استفاده‌ی از گوگل چنین خروجی را دارد:
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, value:Vahid N.
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname, value:Vahid
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname, value:N.
External provider[Google] info-> claim:urn:google:profile, value:https://plus.google.com/105013528531611201860
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress, value:my.name@gmail.com
بنابراین اگر بخواهیم بر اساس این claims بازگشتی از گوگل، کاربر جاری در بانک اطلاعاتی خود را بیابیم، فقط کافی است اطلاعات claim مخصوص emailaddress آن‌را مورد استفاده قرار دهیم:
        [HttpGet]
        public async Task<IActionResult> Callback()
        {
            // ...

            var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
            if (user == null)
            {
                // user wasn't found by provider, but maybe one exists with the same email address?  
                if (provider == "Google")
                {
                    // email claim from Google
                    var email = claims.FirstOrDefault(c =>
                        c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
                    if (email != null)
                    {
                        var userByEmail = await _usersService.GetUserByEmailAsync(email.Value);
                        if (userByEmail != null)
                        {
                            // add Google as a provider for this user
                            await _usersService.AddUserLoginAsync(userByEmail.SubjectId, provider, providerUserId);

                            // redirect to ExternalLoginCallback
                            var continueWithUrlAfterAddingUserLogin =
                                Url.Action("Callback", new {returnUrl = returnUrl});
                            return Redirect(continueWithUrlAfterAddingUserLogin);
                        }
                    }
                }


                var returnUrlAfterRegistration = Url.Action("Callback", new {returnUrl = returnUrl});
                var continueWithUrl = Url.Action("RegisterUser", "UserRegistration",
                    new {returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId});
                return Redirect(continueWithUrl);
            }
در اینجا ابتدا بررسی شده‌است که آیا کاربر جاری واکشی شده‌ی از بانک اطلاعاتی نال است؟ اگر بله، اینبار بجای هدایت مستقیم او به صفحه‌ی ثبت کاربر و تکمیل مشخصات او، مقدار email این کاربر را از لیست claims بازگشتی او از طرف گوگل، استخراج می‌کنیم. سپس بر این اساس اگر کاربری در بانک اطلاعاتی وجود داشت، تنها اطلاعات تکمیلی UserLogin او را که در اینجا خالی است، به اکانت گوگل او متصل می‌کنیم. به این ترتیب دیگر کاربر نیازی نخواهد داشت تا به صفحه‌ی ثبت اطلاعات تکمیلی هدایت شود و یا اینکه بی‌جهت رکورد User جدیدی را مخصوص او به بانک اطلاعاتی اضافه کنیم.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.
اشتراک‌ها
پیاده سازی ارتباطات همیشه رمزنگاری شده در SQL Server 2016

Always Encrypted is a new feature in SQL Server 2016, which encrypts the data both at rest *and* in motion (and keeps it encrypted in memory). So this protects the data from rogue administrators, backup thieves, and man-in-the-middle attacks. Unlike TDE, as well, Always Encrypted allows you to encrypt only certain columns, rather than the entire database.

پیاده سازی ارتباطات همیشه رمزنگاری شده در SQL Server 2016
اشتراک‌ها
JSON Web Token چیست؟

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair using RSA. 

JSON Web Token چیست؟
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
- بله. فقط بجای return Viewها شما بازگشت متداول http status‌ها را خواهید داشت مانند return NotFound و یا return Ok. مابقی کدهای آن تفاوتی نمی‌کند.
- هدف نحوه‌ی نمایش این است که تزریق وابستگی‌های ASP.NET Identity و امکانات تنظیم شده‌ی آن در این مثال، در یک کنترلر Web API هم کار می‌کند.
- اگر هدف صرفا استفاده از Single page applications و Web API است، روش استفاده‌ی JWT متداول‌تر است.