اشتراک‌ها
استفاده از MongoDB .NET Driver با NET Core Web API.

Source could be also accessed from GitHub -> https://github.com/fpetru/WebApiMongoDB.

Problem / solution format brings an easier understanding on how to build things, giving an immediate feedback. Starting from this idea, the blog post will present step by step how to build

a web application to store your ideas in an easy way, adding text notes, either from desktop or mobile, with few characteristics: run fast, save on the fly whatever you write, and be reasonably reliable and secure.

This blog post will implement just the backend, WebApi and the database access, in the most simple way. Next blog post will cover the front end, using Angular. Then, there will be an additional article on how to increase the performance and the security. 

استفاده از MongoDB .NET Driver با NET Core Web API.
مطالب
مسیریابی در Angular - قسمت نهم - محافظ‌های مسیرها
جهت مقاصد امنیتی، اعتبارسنجی کاربران و یا تحت نظر قرار دادن مسیرها، نیاز است بتوان بررسی کرد که آیا پیمایش یک مسیر، مجاز است یا خیر؟ برای پیاده سازی یک چنین ویژگی‌هایی در Angular، مفهوم Route Guards یا محافظ‌های مسیرها پیش بینی شده‌است که شامل چندین نوع محافظ می‌شوند:
 - canActivate : جهت محافظت دسترسی به یک مسیر
 -  canActivateChild: برای محافظت دسترسی به یک Child Route
 - canDeactivate : برای جلوگیری کردن از ترک مسیر جاری و هدایت به مسیری دیگر (برای مثال جهت نمایش پیام «هنوز اطلاع تغییر یافته را ذخیره نکرده‌اید»)
 - canLoad : برای جلوگیری از مسیریابی غیرهمزمان (async routing) که در قسمت بعدی بررسی خواهد شد
 - resolve: برای پیش واکشی اطلاعات، پیش از نمایش مسیر (که آن‌را در قسمت چهارم این سری بررسی کردیم)


لزوم استفاده‌ی از محافظ‌های مسیرها


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


ترتیب اجرای محافظ‌های مسیرها

مسیریاب سیستم، ابتدا محافظ canDeactivate را اجرا می‌کند تا مشخص شود که آیا کاربر می‌تواند مسیر جاری را ترک کند یا خیر؟ سپس اگر مسیریابی تعریف شده غیرهمزمان باشد، محافظ canLoad اجرا می‌شود. پس از آن محافظ canActivateChild بررسی می‌شود. در ادامه محافظ canActivate اجرا می‌گردد. در پایان کار بررسی محافظ‌های موجود، کار بررسی محافظ resolve‌، جهت پیش واکشی اطلاعات مسیر درخواستی، انجام خواهد شد.
در اینجا اگر یکی از محافظ‌ها مقدار false را برگرداند، پردازش مابقی آن‌ها لغو خواهد شد و کار هدایت کاربر به مسیر درخواستی، خاتمه می‌یابد.


مراحل ساخت و اعمال یک محافظ مسیر

ساخت و اعمال یک محافظ مسیر شامل سه مرحله است:
الف) یک محافظ مسیر عموما به صورت یک سرویس جدید پیاده سازی می‌شود:
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {

    }
}
در اینجا برای اینکه این سرویس به صورت یک محافظ مسیر عمل کند، نیاز است نوع محافظ مدنظر را نیز پیاده سازی نماید؛ مانند CanActivate در اینجا. پس از آن باید متد مرتبط با این اینترفیس که در اینجا canActivate است، پیاده سازی شود. اگر این متد false را برگرداند، سبب لغو هدایت کاربر به آن مسیر خواهد شد و این متد می‌تواند خروجی پیچیده‌تری مانند یک Observable را نیز داشته باشد. اگر یک چنین نوع خروجی درنظر گرفته شود، فراخوان آن، تا پایان کار این Observable صبر خواهد کرد.

ب) از آنجائیکه محافظ‌ها، سرویس هستند، نیاز است تعریف کلاس آن‌ها را در قسمت providers ماژول مرتبط نیز ذکر کنیم تا در برنامه قابل دسترسی شوند. باید دقت داشت که برخلاف سایر سرویس‌ها، امکان تعریف محافظ‌ها صرفا در سطح یک ماژول مسیر است و نه در سطح یک کامپوننت. به این ترتیب مسیریاب می‌تواند به آن، در طی هدایت کاربر به مسیر درخواستی، دسترسی پیدا کند.

ج) پس از آن برای فعالسازی یک محافظ مسیر، آن‌را به عنوان یک خاصیت جدید، به تنظیمات مسیریابی اضافه خواهیم کرد. نام این خاصیت دقیقا مساوی با نوع محافظی است که تعریف شده‌است. برای مثال اگر محافظ تعریف شده از نوع CanActivate است، نام خاصیتی که ذکر خواهد شد، canActivate می‌باشد. مقدار آن نیز می‌تواند آرایه‌ای از سرویس‌هایی از این نوع باشد.

امکان به اشتراک گذاشتن یک محافظ بین چندین مسیر نیز وجود دارد. فرض کنید می‌خواهیم تمام مسیرهای مربوط به محصولات را محافظت کنیم. در این حالت می‌توان محافظ را به تک تک Child routes موجود اعمال کرد و یا می‌توان محافظ را به والد آن‌ها نیز اعمال کنیم تا به صورت خودکار سبب محافظت از فرزندان آن نیز شویم.


یک مثال: ساخت محافظ canActivate‌

جهت بررسی شرط یا شرایطی پیش از فعال سازی یک مسیر درخواستی، از محافظ‌هایی از نوع canActivate می‌توان استفاده کرد. این نوع محافظ‌ها عموما جهت اعتبارسنجی کاربران و محدود سازی دسترسی آن‌ها به قسمت‌های مختلف برنامه استفاده می‌شوند. این نوع محافظ‌ها حتی با تغییر پارامترهای مسیریابی نیز فعال شده و بررسی می‌شوند.

در ادامه‌ی مثال این سری می‌خواهیم کاربران را پیش از دسترسی به قسمت‌های مختلف مرتبط با محصولات، وادار به لاگین کنیم. برای این منظور دستور ذیل را اجرا کنید:
 >ng g guard user/auth -m user/user.module
به این ترتیب تغییرات ذیل در ماژول کاربران رخ خواهند داد:
 installing guard
  create src\app\user\auth.guard.spec.ts
  create src\app\user\auth.guard.ts
  update src\app\user\user.module.ts
در اینجا قالب ابتدایی کلاس سرویس AuthGuard ایجاد می‌شود (در فایل auth.guard.ts) و همچنین اگر به سطر آخر آن دقت کنید، این سرویس را به قسمت providers ماژول کاربران (در فایل user.module.ts) نیز افزوده‌است.

در ادامه کدهای این محافظ را به صورت ذیل تکمیل کنید:
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivate, Router } from '@angular/router';

import { AuthService } from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService,
    private router: Router) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.checkLoggedIn(state.url);
  }

  checkLoggedIn(url: string): boolean {
    if (this.authService.isLoggedIn()) {
      return true;
    }
    this.authService.redirectUrl = url;
    this.router.navigate(['/login']);
    return false;
  }
}
خاصیت redirectUrl نیز به کلاس سرویسAuthService ، جهت به اشتراک گذاری اطلاعات، اضافه شده‌است:
export class AuthService {
   currentUser: IUser;
   redirectUrl: string;

توضیحات:

این سرویس چون از نوع CanActivate است، این اینترفیس را پیاده سازی کرده‌است و همچنین متد canActivate آن‌را نیز به همراه دارد:
export class AuthGuard implements CanActivate {
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
در اینجا از ActivatedRouteSnapshot می‌توان اطلاعات مسیرجاری، مانند پارامترهای آن‌را بدست آورد. پارامتر RouterStateSnapshot نیز وضعیت مسیریابی را بازگشت می‌دهد. برای مثال state.url، حاوی آدرس کامل مسیر درخواستی به صورت یک رشته است که از آن در اینجا جهت حفظ و به اشتراک گذاری مسیر اولیه‌ی درخواستی استفاده شده‌است. خاصیت route.url حاوی آرایه‌ای از URL segments است.

یک نکته: هرچند در اینجا می‌توان به پارامتر id مسیر، مانند route.params['id'] در صورت نیاز دسترسی یافت، اما امکان دسترسی به اطلاعات از پیش واکشی شده مانند route.data['product'] وجود ندارد. علت آن‌را نیز در قسمت «ترتیب اجرای محافظ‌های مسیرها» ابتدای بحث جاری، بررسی کردیم: محافظ resolve در انتهای کار پردازش تمام محافظ‌های موجود فراخوانی می‌شود.

در متد canActivate می‌خواهیم بررسی کنیم که آیا کاربر، لاگین کرده‌است یا خیر؟ اگر بله، تنها کافی است true را بازگشت دهیم تا کار این محافظ پایان یابد. در غیراینصورت false را بازگشت داده و همچنین سبب هدایت کاربر به صفحه‌ی لاگین می‌شویم.
به همین منظور سرویس AuthService را به سازنده‌ی این کلاس تزریق کرده‌ایم تا بتوانیم به متد isLoggedIn آن دسترسی پیدا کنیم (این سرویس را در قسمت دوم این سری تکمیل کردیم).
این متد نیز به صورت ذیل تعریف شده‌است:
isLoggedIn(): boolean {
   return !this.currentUser;
}
در اینجا استفاده‌ی از ! سبب بازگشت true، در صورت نال نبودن شیء کاربر جاری وارد شده‌ی به سیستم می‌شود.

در ادامه برای استفاده‌ی از این محافظ مسیر، به فایل src\app\product\product-routing.module.ts مراجعه کرده و آن‌را به نحو ذیل اعمال خواهیم کرد:
import { AuthGuard } from './../user/auth.guard';

const routes: Routes = [
  {
    path: 'products',
    canActivate: [ AuthGuard ],
    children: [    ]
  }
];
در قسمت ششم، کار گروه بندی مسیرها را انجام دادیم. اکنون در اینجا نمونه‌ای از استفاده‌ی از آن‌را مشاهده می‌کنید. بجای اینکه AuthGuard  را به تک تک مسیرهای فرزند تعریف شده‌ی محصولات، اعمال کنیم، آن‌را به والد این مسیر اعمال کرده‌ایم تا به صورت خودکار به تمام فرزندان آن نیز اعمال شود.

اکنون برنامه را با دستور ng s -o ساخته و اجرا کنید. سپس بر روی لینک لیست محصولات و یا افزودن یک محصول جدید کلیک کنید. بلافاصله صفحه‌ی لاگین را مشاهده خواهید کرد.


به خاطر سپاری و بازیابی مسیر درخواستی کاربر پس از لاگین

در اینجا اگر کاربر بر روی لینک افزودن یک محصول جدید کلیک کند، صفحه‌ی لاگین را مشاهده خواهد کرد. اما پس از لاگین، همواره به مسیر لیست محصولات هدایت می‌شود و در این حالت مسیر درخواستی اولیه فراموش خواهد شد. برای رفع این مشکل نیاز است آدرس درخواستی کاربر را نیز ذخیره و بازیابی کرد. به همین جهت خاصیت this.authService.redirectUrl = url را در متد checkLoggedIn محافظ تعریف شده مقدار دهی کردیم. در اینجا از سرویس Auth، برای به اشتراک گذاری اطلاعات با محافظ‌های مسیر استفاده کرده‌ایم. طول عمر یک سرویس، singleton است. بنابراین تنها یک وهله از آن در طول عمر برنامه وجود خواهد داشت. به این ترتیب با ذخیره‌ی اطلاعاتی در آن، این اطلاعات در تمام برنامه قابل دسترسی خواهد شد.
با توجه به این نکته، اکنون به فایل src\app\user\login\login.component.ts مراجعه کرده و قسمت this.router.navigate آن‌را به صورت ذیل بهبود خواهیم بخشید:
      if (this.authService.login(userName, password)) {
        if (this.authService.redirectUrl) {
          this.router.navigateByUrl(this.authService.redirectUrl);
        } else {
          this.router.navigate(['/products']);
        }
      }
در اینجا بررسی می‌شود که آیا پیشتر خاصیت redirectUrl پس از لاگین مقدار دهی شده‌است یا خیر؟ اگر بله، از متد navigateByUrl جهت هدایت به آن مسیر استفاده خواهد شد.

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


بررسی محافظ canActivateChild

این محافظ نیز شبیه به محافظ canActivate است؛ با این تفاوت که تنها زمانی فعالسازی خواهد شد که فرزند یک مسیر قرار است نمایش داده شود و نه خود مسیر اصلی.
محافظ canActivateChild با تغییر قسمت child یک مسیر فعالسازی می‌شود؛ حتی اگر این تغییر در حد تغییر پارامترهای آن مسیر باشد. اما باید درنظر داشت که اگر تنها قسمت child یک مسیر تغییر کند، دیگر محافظ canActivate مجددا اجرا نخواهد شد.

یک مثال: اگر کاربر در حال مشاهده‌ی صفحه‌ی لیست محصولات باشد و بر روی لینک مشاهده‌ی یک محصول کلیک کند، تنها قسمت child مسیر تغییر می‌کند. در این حالت canActivate مسیر اصلی دیگر اجرا نخواهد شد؛ اما تمام محافظ‌های canActivateChild مرتبط مجددا اجرا خواهند شد.


بررسی محافظ canDeactivate

محافظ canDeactivate پیش از ترک یک مسیر، فعالسازی و بررسی می‌شود. عموما از آن جهت بررسی وضعیت اطلاعات ذخیره نشده و اطلاع رسانی به کاربر، پیش از ترک مسیر جاری استفاده استفاده می‌گردد. این محافظ با هر تغییری در آدرس جاری مسیر، بررسی می‌شود. بدیهی است این تغییر صرفا درون یک برنامه‌ی Angular معنا پیدا می‌کند و نه هدایت به سایتی دیگر.
در حال حاضر در مثال جاری این سری، اگر کاربر، تغییری را در صفحه‌ی ویرایش اطلاعات ایجاد کند و بدون کلیک بر روی دکمه‌ی Save به صفحه‌ی دیگری مراجعه کند، این اطلاعات تغییر یافته، از دست خواهند رفت. برای رفع این مشکل می‌توان محافظ canDeactivate ایی را برای آن طراحی کرد. به همین جهت دستور ذیل را اجرا کنید:
 >ng g guard product/ProductEdit -m product/product.module
تا سبب انجام تغییرات ذیل در ماژول محصولات شود:
 installing guard
  create src\app\product\product-edit.guard.spec.ts
  create src\app\product\product-edit.guard.ts
  update src\app\product\product.module.ts
در اینجا علاوه بر ایجاد قالب ابتدایی محافظ ProductEdit، سبب به روز رسانی قسمت providers ماژول محصولات نیز شده‌است.

امضای ابتدایی یک محافظ CanDeactivate به صورت ذیل است:
export  class ProductEditGuard implements CanDeactivate<ProductEditComponent> {
    canDeactivate(component: ProductEditComponent): boolean {
اینترفیس CanDeactivate جنریک بوده و پارامتر جنریک آن نوع کامپوننتی را که قرار است از این محافظ استفاده کند، مشخص می‌کند. سپس نوع پارامتر متد canDeactivate آن بر اساس نوع پارامتر جنریک، تعیین می‌گردد.
اکنون این محافظ نیاز دارد تا بداند که آیا کامپوننت ویرایش محصولات، دارای اطلاعات ذخیره نشده‌ای هست یا خیر؟ چون کامپوننت ویرایش محصولات، به عنوان پارامتر به متد canDeactivate آن ارسال شده‌است، بنابراین می‌تواند به خواص و متد‌های عمومی آن کلاس نیز دسترسی پیدا کند. به همین جهت تغییرات ذیل را به کامپوننت ویرایش محصولات در فایل src\app\product\product-edit\product-edit.component.ts اعمال می‌کنیم:
  get product(): IProduct {
    return this.currentProduct;
  }
  set product(value: IProduct) {
    this.currentProduct = value;
    // Clone the object to retain a copy
    this.originalProduct = Object.assign({}, value);
  }

  get isDirty(): boolean {
    return JSON.stringify(this.originalProduct) !== JSON.stringify(this.currentProduct);
  }
در اینجا یک کپی از اصل محصول در حال ویرایش، برای مقایسه‌ی آن با محصول جاری در حال ویرایش، نگهداری می‌شود. به این ترتیب خاصیت isDirty می‌تواند مشخص کند که آیا تغییری بر روی خواص این شیء صورت گرفته‌است یا خیر؟ استفاده از متد JSON.stringify، یکی از ساده‌ترین روش‌هایی است که از آن می‌توان جهت مقایسه‌ی تمام خواص دو شیء استفاده کرد. البته چون در اینجا ترتیب خواص این دو شیء یکی است، این روش کار می‌کند.
برای اینکه این امر میسر شود، خاصیت product به حالت get/set دار تغییر یافته‌است تا بتوان کپی اولیه‌ی محصول را جهت مقایسه، نگهداری کرد. استفاده از متد Object.assign سبب ایجاد یک کپی از شیء اولیه شده و به این صورت دو وهله‌ی غیرمشترک را خواهیم داشت. اگر value مستقیما به originalProduct  انتساب داده می‌شد، در این حالت هر دوی currentProduct و originalProduct به یک شیء اشاره می‌کردند.

اکنون می‌توان از این خاصیت جدید کامپوننت ویرایش محصولات، در محافظ ترک صفحه‌ی آن استفاده کرد:
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';

import { ProductEditComponent } from './product-edit/product-edit.component';

@Injectable()
export class ProductEditGuard implements CanDeactivate<ProductEditComponent> {

  canDeactivate(component: ProductEditComponent): boolean {
    if (component.isDirty) {
      let productName = component.product.productName || 'New Product';
      return confirm(`Navigate away and lose all changes to ${productName}?`);
    }
    return true;
  }
}
در اینجا اگر فرم، تغییر یافته و هنوز ذخیره نشده باشد، خاصیت isDirty برقرار شده و سبب نمایش یک دیالوگ confirm می‌شود. اگر کاربر آن‌را تائید کند، آنگاه مسیر درخواستی جدید فعال می‌شود. در غیراینصورت، هدایت به مسیر جدید لغو خواهد شد.

در آخر برای استفاده‌ی از این محافظ جدید، باید آن‌را به تنظیمات مسیریابی برنامه اضافه کنیم. به همین جهت به فایل src\app\product\product-routing.module.ts مراجعه کرده و این محافظ را به والد مسیریابی ویرایش یک محصول اضافه می‌کنیم:
import { ProductEditGuard } from './product-edit.guard';

const routes: Routes = [
  {
    path: 'products',
    canActivate: [ AuthGuard ],    
    children: [
      {
        path: '',
        component: ProductListComponent
      },
      {
        path: ':id',
        component: ProductDetailComponent,
        resolve: { product: ProductResolverService }
      },
      {
        path: ':id/edit',
        component: ProductEditComponent,
        resolve: { product: ProductResolverService },
        canDeactivate: [ ProductEditGuard ],
        children: [
          { path: '', redirectTo: 'info', pathMatch: 'full' },
          { path: 'info', component: ProductEditInfoComponent },
          { path: 'tags', component: ProductEditTagsComponent }
        ]
      }
    ]
  }
];
با افزودن canDeactivate به والد ویرایش محصولات، از هر دو child route تعریف شده محافظت می‌کند.


برای آزمایش آن، به صفحه‌ی ویرایش یکی از محصولات مراجعه کرده و تغییری را ایجاد کنید. سپس درخواست مشاهده‌ی صفحه‌ی دیگری را با کلیک بر روی یکی از لینک‌های منوی برنامه ارائه دهید. بلافاصله دیالوگ confirm ظاهر خواهد شد (تصویر فوق).

مشکل! در همین حالت بر روی دکمه‌ی Ok کلیک کنید تا اطلاعات ذخیره نشده را از دست داده و به مسیر دیگری هدایت شویم. مجددا همین پروسه را تکرار کنید. اینبار اگر بر روی دکمه‌ی Save کلیک کنید، باز هم دیالوگ confirm ظاهر می‌شود. علت اینجا است که شیء محصول اصلی و جاری، پس از ذخیره سازی به حالت اولیه بازگشت داده نشده‌اند. برای این منظور متد reset را به کامپوننت ویرایش اطلاعات اضافه کرده:
reset(): void {
    this.dataIsValid = null;
    this.currentProduct = null;
    this.originalProduct = null;
  }
و سپس آن‌را به متد onSaveComplete، اضافه می‌کنیم:
  onSaveComplete(message?: string): void {
    if (message) {
      this.messageService.addMessage(message);
    }
    this.reset();

    // Navigate back to the product list
    this.router.navigate(['/products']);
  }


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-routing-lab-08.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب
رمزنگاری JWT و افزایش امنیت آن در ASP.NET Core
آموزش JSON Web Token (به اختصار JWT) و پیاده سازی آن در برنامه‌های ASP.NET Core درسایت موجود است.
توکن JWT در حالت عادی به صورت Base64 رمزنگاری می‌شود که این نوع رمزنگاری به راحتی قابل رمزگشایی و خواندن است. سایت‌های آنلاین زیادی برای رمزگشایی base64 موجود است؛ برای مثال کافی است توکن خود را در سایت jwt.io کپی کنید و به راحتی محتوای بدنه توکن (Payload) را مشاهده کنید.

پس توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد.
ساده‌ترین راه حل، رمزنگاری دستی بدنه توکن می‌باشد که مثلا بر اساس کلیدی (که فقط سمت سرور نگهداری و مراقبت می‌شود) توکن را رمزنگاری کرده و به هنگام خواندن، آن را با همان کلید رمزگشایی کنیم. ولی این روش ضمن استاندارد نبودن، مشکلات خاص خودش را دارد و نیاز به سفارشی سازی زیادی، هم به هنگام تولید توکن و هم به هنگام خواندن توکن دارد.
 اصولی‌ترین راه، استفاده از رمزنگاری توکن به روش JSON Web Encryption (یا به اختصار JWE) است که در آن مشابه روش بالا ولی به صورت استاندارد تعریف شده (و قابل فهم برای همه استفاده کنندگانی که با این استاندارد آشنایی دارند) است.
نکته :
  1. اگر از JWE استفاده نمی‌کنید، بهتر است اطلاعات حساسی مانند شماره تلفن کاربر (و شاید در مواردی حتی آیدی کاربر) را در بدنه توکن قرار ندهیم چرا که قابل خوانده شدن است (که در این صورت استفاده از Guid برای آیدی کاربر می تواند کمی مفید باشد چرا که حداقل آیدی بقیه کاربران قابل پیش بینی نمی‌باشد).
  2. توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد؛ ولی به لطف امضای (signature) آن، در برابر تغییر محتوا، ایمن است؛ چرا که در صورت تغییر محتوای آن، دیگر مقدار hash محتوا با امضای آن همخوانی نداشته و عملا از اعتبار ساقط می‌گردد.
برای رمزنگاری JWT باید در هر دو مرحله‌ی "تولید توکن" و "اعتبارسنجی توکن" کلید و الگوریتم لازم برای رمزنگاری را مشخص کنیم. بدین منظور در جایی که توکن را تولید می‌کنیم، خواهیم داشت :
var secretKey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey"); // must be 16 character or longer 
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);

var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey"); //must be 16 character
var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);

var claims = new List<Claim>
{
   new Claim(ClaimTypes.Name, "UserName"), //user.UserName
   new Claim(ClaimTypes.NameIdentifier, "123"), //user.Id
};

var descriptor = new SecurityTokenDescriptor
{
   Issuer = _siteSetting.JwtSettings.Issuer,
   Audience = _siteSetting.JwtSettings.Audience,
   IssuedAt = DateTime.Now,
   NotBefore = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.NotBeforeMinutes),
   Expires = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.ExpirationMinutes),
   SigningCredentials = signingCredentials,
   EncryptingCredentials = encryptingCredentials,
   Subject = new ClaimsIdentity(claims)
};

var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(descriptor);
string encryptedJwt = tokenHandler.WriteToken(securityToken);
کد بالا، مانند کد تولید یک توکن jwt معمولی است؛ تنها تفاوت آن، ایجاد و معرفی شیء encryptingCredentials است.
در خط چهارم، آرایه بایتی کلید لازم برای رمزنگاری (encryptionkey) گرفته شده و از روی آن encryptingCredentials ایجاد شده‌است. این کلید باید 16 کاراکتر باشد؛ در غیر اینصورت به هنگام تولید توکن، خطا دریافت خواهید کرد. رمزنگاری توکن، توسط این کلید و الگوریتم مشخص شده انجام خواهد شد.
سپس شیء تولید شده، به خاصیت EncryptingCredentials کلاس SecurityTokenDescriptor معرفی شده‌است و  نهایتا متد tokenHandler.WriteToken توکن رمزنگاری شده‌ای را تولید می‌کند.
نتیجه کار این است که توکن تولید شده، بدون کلید مربوطه (که سمت سرور نگهداری می‌شود) قابل رمز گشایی نیست و اگر آن را در سایت jwt.io کپی کنید، جوابی دریافت نخواهید کرد.

در ادامه لازم است در مرحله اعتبار سنجی و رمزگشایی توکن در سمت سرور، کلید و الگوریتم لازم را به آن معرفی کنیم تا middleware مربوطه بتواند توکن دریافتی را رمزگشایی و سپس اعتبار سنجی کند. بدین منظور در متد ConfigureServices کلاس Startup.cs خواهیم داشت:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
   var secretkey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey");
   var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey");

   var validationParameters = new TokenValidationParameters
   {
      ClockSkew = TimeSpan.Zero, // default: 5 min
      RequireSignedTokens = true,

      ValidateIssuerSigningKey = true,
      IssuerSigningKey = new SymmetricSecurityKey(secretkey),

      RequireExpirationTime = true,
      ValidateLifetime = true,

      ValidateAudience = true, //default : false
      ValidAudience = "MyWebsite",

      ValidateIssuer = true, //default : false
      ValidIssuer = "MyWebsite",

      TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey)
   };

   options.RequireHttpsMetadata = false;
   options.SaveToken = true;
   options.TokenValidationParameters = validationParameters;
});

کد بالا مانند کد فعال سازی احراز هویت توسط JWT معمولی در ASP.NET Core است؛ با این تفاوت که:

ابتدا آرایه بایتی همان کلید رمزنگاری (encryptionkey) که قبلا توکن را با آن رمزنگاری کرده بودیم، گرفته شده و سپس توسط مقداردهی خاصیت TokenDecryptionKey کلاس TokenValidationParameters، معرفی شده است. 

ولی شاید این سؤال برایتان پیش آید که چرا الگوریتم رمزنگاری مشخص نشده است؟ پس سرور از کجا می‌فهمد که این توکن بر اساس چه الگوریتمی رمزنگاری شده است؟ 

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

{
  "alg": "A128KW",
  "enc": "A128CBC-HS256",
  "typ": "JWT"
}

پس سرور بر اساس این قسمت از توکن (header)، که هیچگاه رمزنگاری نمی‌شود، می‌فهمد که توسط چه الگوریتمی باید توکن را رمزگشایی کند که در اینجا A128CBC-HS256 (اختصار AES-128-CBC و HMAC-SHA256) است.

مثال کامل و قابل اجرای این مطلب را می‌توانید از این ریپازیتوری دریافت کنید.

اشتراک‌ها
مقایسه اپلیکیشن های Native اندروید و آی او اس با React Native، Ionic و Native ُScript

NativeScript یک فریمورک متن باز برای ساخت برنامه‌های نیتیو موبایل با جاوا اسکریپت است.

Ionic فریمورک مبتنی بر Angular، به صورت متن باز برای توسعه نرم افزار تلفن همراه بکار می‌رود که باعث می‌شود برنامه‌های نیتیو و پیشرفته وب با تکنولوژی‌های بالا ساخته شود.

React Native یک فریمورک جاوا اسکریپت برای ساخت برنامه‌های تلفن همراه نیتیو است که از فریمورک React استفاده می‌کند.

انتخاب یکی از این 3 گزینه در دنیای صنعت رو به رشد امروز دشوار است. 

مقایسه اپلیکیشن های Native اندروید و آی او اس با React Native، Ionic و Native ُScript
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
با سلام؛ دو تا سوال نسبت به مقاله فوق برای من پیش اومده 
1) اول اینکه امکان جایگزین کردن Dapper به جای EntityFramework وجود داره یا خیر ؟
2) آیا استفاده از پایگاه داده‌های NoSQL مثل MongoDB به جای SqlServer برای سناریو فوق فکر خوبی است ؟ اگر بله آیا پیاده سازی آن مبتنی بر Identity ممکن است یا خیر ؟
البته یک سوال کلی هم دارم که ممکن به این بحث مربوط نشه، کلا برای مواردی مثل ثبت لاگ، ثبت جزییات خطاها، اطلاعات کاربران،  ورود و خروج‌ها بهتر نیست از NoSQL‌ها استفاده کنیم ؟
اشتراک‌ها
تم ادمین Angular 6 و Bootstrap 4

تم آنگولار راستچین و دارای 3 نوع تم به همراه ویژگی‌های زیر

  • Angular 6+ & Typescript
  • Bootstrap 4+ & SCSS
  • Responsive layout
  • RTL support
  • High resolution
  • Flexibly configurable themes with hot-reload (2 themes included)
  • Authentication module with multiple providers
  • Lots of awesome features:
    • Buttons
    • Modals
    • Popovers
    • Icons
    • Typography
    • Animated searches
    • Forms
    • Tabs
    • Notifications
    • Tables
    • Maps
    • Charts
    • Editors

And many more! 

تم ادمین Angular 6 و Bootstrap 4
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران
مباحث پایه‌ای اعتبارسنجی کاربران در ASP.NET Core با نگارش‌های پیشین ASP.NET MVC، آنچنان تفاوت ساختاری مهمی ندارند و یکی هستند. عمده‌ی تفاوت‌ها در نحوه‌ی برپایی تنظیمات اولیه‌ی اسکریپت‌های آن‌ها و همچنین یک سری Tag Helper جدید معادل با HTML Helperهای متداول اعتبارسنجی هستند.


دریافت وابستگی‌های سمت کاربر مباحث اعتبارسنجی

زمانیکه گزینه‌ی ایجاد یک پروژه‌ی جدید ASP.NET Core را در VS.NET انتخاب می‌کنیم، علاوه بر قالب empty آن، قالب دیگری به نام web application نیز در آن موجود است. با انتخاب این قالب، فایلی را به نام bower.json نیز با این محتوا مشاهده می‌کنید:
 {
  "name": "asp.net",
  "private": true,
  "dependencies": {
"bootstrap": "3.3.6",
"jquery": "2.2.0",
"jquery-validation": "1.14.0",
"jquery-validation-unobtrusive": "3.2.6"
  }
}
این مداخل تمام پیشنیازهای مباحث اعتبارسنجی کاربران را به صورت خودکار به پروژه اضافه می‌کنند. بنابراین افزودن فایل جدید bower.json به پروژه با محتوای فوق و سپس مراجعه به solution explorer و کلیک راست بر روی گره dependencies و در آخر انتخاب restore packages، سبب دریافت خودکار این بسته‌ها می‌شود.


این بسته‌ها را پس از دریافت، در پوشه‌ی bower_components خواهید یافت:


البته باید دقت داشت که استفاده از bower در اینجا الزامی نیست. اگر علاقمند بودید از npm و node.js استفاده کنید.


افزودن وابستگی‌های سمت کاربر مباحث اعتبارسنجی و عمومی کردن آن‌ها

پس از دریافت وابستگی‌های مورد نیاز توسط bower، به فایل layout برنامه مراجعه کرده و سپس آن‌ها را به ترتیب ذیل اضافه می‌کنیم:
    <script src="~/bower_components/jquery/dist/jquery.min.js"></script>
    <script src="~/bower_components/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
 
    @RenderSection("scripts", required: false)
</body>
</html>
هرچند این ترتیب درست است، اما ... کار نمی‌کند. علت را هم در مبحث «ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک» بررسی کردیم: تمام پوشه‌های موجود در ریشه‌ی یک برنامه‌ی ASP.NET Core، غیرعمومی هستند (و توسط کاربران قابل درخواست نیستند)، مگر اینکه آن‌ها‌را توسط میان افزار static files، عمومی کنیم. برای این منظور به کلاس آغازین برنامه مراجعه کرده و سپس به متد Configure آن، تنظیمات ذیل را اضافه کنید:
// Serve wwwroot as root
app.UseFileServer();
 
// Serve /bower_components as a separate root
app.UseFileServer(new FileServerOptions
{
    // Set root of file server
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "bower_components")),
    // Only react to requests that match this path
    RequestPath = "/bower_components",
    // Don't expose file system
    EnableDirectoryBrowsing = false
});
به این ترتیب فایل‌های موجود در پوشه‌ی bower_components به مسیر bower_components/ نگاشت می‌شوند. البته انتخاب مقدار RequestPath در اینجا اختیاری است و برای مثال می‌توانید مسیر scripts/ را نیز معرفی کنید (نگاشت مسیر فیزیکی و واقعی فایل‌ها به URL خاص دیگری که توسط وب سرور ارائه خواهد شد).
اگر RequestPath را به مسیر دیگری تنظیم کردید، نیاز است ابتدای سه مدخل ذکر شده را بر این اساس اصلاح کنید، تا فایل‌ها توسط وب سرور قابل ارائه شوند.


استفاده از CDN برای توزیع اسکریپت‌های اعتبارسنجی مورد نیاز

در مورد environment tag helper در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» پیشتر بحث شد. در اینجا نیز می‌توان برای مثال در حال توسعه، از اسکریپت‌های محلی
<environment name="Development">
   <script src="~/bower_components/jquery/dist/jquery.min.js"></script>
   <script src="~/bower_components/jquery-validation/dist/jquery.validate.min.js"></script>
   <script src="~/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
</environment>
و در حالت نهایی توزیع برنامه، از CDNهای ارائه‌ی اسکریپت‌ها، جهت کاهش بار سرور استفاده کرد:
<environment names="Staging, Production">
   <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"
asp-fallback-src="/bower_components/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery">
  </script>
   <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
asp-fallback-src="bower_components/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator">
  </script>
   <script src="https://ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js"
asp-fallback-src="/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive">
  </script>
</environment>
در اینجا fallback به آدرس محلی دریافت اسکریپت‌های مدنظر، در صورت در دسترس نبودن CDN، تنظیم شده‌است.
روش عملکرد fallback هم به این صورت است که بررسی می‌شود آیا عبارت ذکر شده‌ی در قسمت asp-fallback-test قابل اجرا است یا خیر؟ اگر خیر، یعنی CDN قابل دسترسی نیست و از نمونه‌ی محلی استفاده می‌کند.


خلاصه‌ای از Tag helpers اعتبارسنجی

در جدول «راهنمای تبدیل HTML Helpers به Tag Helpers» مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers»، معادل‌های HTML Helpers مباحث اعتبارسنجی را نیز ملاحظه کردید. خلاصه‌ی تکمیلی آن به صورت ذیل است:

ValidationSummary.All سبب نمایش خطاهای اعتبارسنجی خواص و همچنین کل مدل می‌شود:
@Html.ValidationSummary(false)
معادل است با
<div asp-validation-summary="All"></div>

ValidationSummary.ModelOnly صرفا خطاهای اعتبارسنجی در سطح مدل را نمایش می‌دهد:
@Html.ValidationSummary(true)
معادل است با
<div asp-validation-summary="ModelOnly"></div>

و برای تعیین نمایش خطاهای اعتبارسنجی یک خاصیت از مدل:
@Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" })
معادل است با
<span asp-validation-for="UserName" class="text-danger"></span>