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.
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!
ده گام برای امنیت نرم افزار
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.
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
ارائهی 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>
مشکل! برخلاف برنامههای 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" }
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); } } }
تامین 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>
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") ) ); } } }
در اینجا نیز مقدار دهی خودکار httpClient.DefaultRequestHeaders.Authorization را مشاهده میکنید که مقدار token خودش را از Local Storage دریافت میکند که کلید متناظر با آنرا در پروژهی BlazorServer.Common به صورت زیر تعریف کردهایم:
namespace BlazorServer.Common { public static class ConstantKeys { // ... public const string LocalToken = "JWT Token"; } }
- همچنین در اینجا به کمک متد 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>(); // ... } } }
@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.
- در متد 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 ثالث در 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
• نام: همان مقدار پیشفرض آن
• Authorized JavaScript origins: آنرا خالی بگذارید.
• Authorized redirect URIs: این مورد همان callback address مربوط به IDP ما است که در اینجا آنرا با آدرس زیر مقدار دهی خواهیم کرد.
https://localhost:6001/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" } } }
ب) تکمیل اطلاعات گوگل در کلاس آغازین برنامه
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"]; }); }
- 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); }
در اینجا نحوهی اصلاح اکشن متد 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}"); }
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
[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); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی 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 وارد کنید.
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.
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.
- هدف نحوهی نمایش این است که تزریق وابستگیهای ASP.NET Identity و امکانات تنظیم شدهی آن در این مثال، در یک کنترلر Web API هم کار میکند.
- اگر هدف صرفا استفاده از Single page applications و Web API است، روش استفادهی JWT متداولتر است.