احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: ده دقیقه

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



افزودن کامپوننت دسترسی به منابع محافظت شده، به ماژول Dashboard

در اینجا قصد داریم صفحه‌ای را به برنامه اضافه کنیم تا در آن بتوان اطلاعات کنترلرهای محافظت شده‌ی سمت سرور، مانند MyProtectedAdminApiController (تنها قابل دسترسی توسط کاربرانی دارای نقش Admin) و MyProtectedApiController (قابل دسترسی برای عموم کاربران وارد شده‌ی به سیستم) را دریافت و نمایش دهیم. به همین جهت کامپوننت جدیدی را به ماژول Dashboard اضافه می‌کنیم:
 >ng g c Dashboard/CallProtectedApi
سپس به فایل dashboard-routing.module.ts ایجاد شده مراجعه کرده و مسیریابی کامپوننت جدید ProtectedPage را اضافه می‌کنیم:
import { CallProtectedApiComponent } from "./call-protected-api/call-protected-api.component";

const routes: Routes = [
  {
    path: "callProtectedApi",
    component: CallProtectedApiComponent,
    data: {
      permission: {
        permittedRoles: ["Admin", "User"],
        deniedRoles: null
      } as AuthGuardPermission
    },
    canActivate: [AuthGuard]
  }
];
توضیحات AuthGuard و AuthGuardPermission را در قسمت قبل مطالعه کردید. در اینجا هدف این است که تنها کاربران دارای نقش‌های Admin و یا User قادر به دسترسی به این مسیر باشند.
لینکی را به این صفحه نیز در فایل header.component.html به صورت ذیل اضافه خواهیم کرد تا فقط توسط کاربران وارد شده‌ی به سیستم (isLoggedIn) قابل مشاهده باشد:
<li *ngIf="isLoggedIn" routerLinkActive="active">
        <a [routerLink]="['/callProtectedApi']">‍‍Call Protected Api</a>
</li>


نمایش و یا مخفی کردن قسمت‌های مختلف صفحه بر اساس نقش‌های کاربر وارد شده‌ی به سیستم

در ادامه می‌خواهیم دو دکمه را بر روی صفحه قرار دهیم تا اطلاعات کنترلرهای محافظت شده‌ی سمت سرور را بازگشت دهند. دکمه‌ی اول قرار است تنها برای کاربر Admin قابل مشاهده باشد و دکمه‌ی دوم توسط کاربری با نقش‌های Admin و یا User.
به همین جهت call-protected-api.component.ts را به صورت ذیل تغییر می‌دهیم:
import { Component, OnInit } from "@angular/core";
import { AuthService } from "../../core/services/auth.service";

@Component({
  selector: "app-call-protected-api",
  templateUrl: "./call-protected-api.component.html",
  styleUrls: ["./call-protected-api.component.css"]
})
export class CallProtectedApiComponent implements OnInit {

  isAdmin = false;
  isUser = false;
  result: any;

  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.isAdmin = this.authService.isAuthUserInRole("Admin");
    this.isUser = this.authService.isAuthUserInRole("User");
  }

  callMyProtectedAdminApiController() {
  }

  callMyProtectedApiController() {
  }
}
در اینجا دو خاصیت عمومی isAdmin و isUser، در اختیار قالب این کامپوننت قرار گرفته‌اند. مقدار دهی آن‌ها نیز توسط متد isAuthUserInRole که در قسمت قبل توسعه دادیم، انجام می‌شود. اکنون که این دو خاصیت مقدار دهی شده‌اند، می‌توان از آن‌ها به کمک یک ngIf، به صورت ذیل در قالب call-protected-api.component.html جهت مخفی کردن و یا نمایش قسمت‌های مختلف صفحه استفاده کرد:
<button *ngIf="isAdmin" (click)="callMyProtectedAdminApiController()">
  Call Protected Admin API [Authorize(Roles = "Admin")]
</button>

<button *ngIf="isAdmin || isUser" (click)="callMyProtectedApiController()">
  Call Protected API ([Authorize])
</button>

<div *ngIf="result">
  <pre>{{result | json}}</pre>
</div>


دریافت اطلاعات از کنترلرهای محافظت شده‌ی سمت سرور

برای دریافت اطلاعات از کنترلرهای محافظت شده، باید در قسمتی که HttpClient درخواست خود را به سرور ارسال می‌کند، هدر مخصوص Authorization را که شامل توکن دسترسی است، به سمت سرور ارسال کرد. این هدر ویژه را به صورت ذیل می‌توان در AuthService تولید نمود:
  getBearerAuthHeader(): HttpHeaders {
    return new HttpHeaders({
      "Content-Type": "application/json",
      "Authorization": `Bearer ${this.getRawAuthToken(AuthTokenType.AccessToken)}`
    });
  }

روش دوم انجام اینکار که مرسوم‌تر است، اضافه کردن خودکار این هدر به تمام درخواست‌های ارسالی به سمت سرور است. برای اینکار باید یک HttpInterceptor را تهیه کرد. به همین منظور فایل جدید app\core\services\auth.interceptor.ts را به برنامه اضافه کرده و به صورت ذیل تکمیل می‌کنیم:
import { Injectable } from "@angular/core";
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from "@angular/common/http";
import { Observable } from "rxjs/Observable";

import { AuthService, AuthTokenType } from "./auth.service";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private authService: AuthService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const accessToken = this.authService.getRawAuthToken(AuthTokenType.AccessToken);
    if (accessToken) {
      request = request.clone({
        headers: request.headers.set("Authorization", `Bearer ${accessToken}`)
      });
    }

    return next.handle(request);
  }
}
در اینجا یک clone از درخواست جاری ایجاد شده و سپس به headers آن، یک هدر جدید Authorization که به همراه توکن دسترسی است، اضافه خواهد شد.
به این ترتیب دیگری نیازی نیست تا به ازای هر درخواست و هر قسمتی از برنامه، این هدر را به صورت دستی تنظیم کرد و اضافه شدن آن پس از تنظیم ذیل، به صورت خودکار انجام می‌شود:
import { HTTP_INTERCEPTORS } from "@angular/common/http";

import { AuthInterceptor } from "./services/auth.interceptor";

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class CoreModule {}
در اینجا نحوه‌ی معرفی این HttpInterceptor جدید را به قسمت providers مخصوص CoreModule مشاهده می‌کنید.

در این حالت اگر برنامه را اجرا کنید، خطای ذیل را در کنسول توسعه‌دهنده‌های مرورگر مشاهده خواهید کرد:
compiler.js:19514 Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1
در سازنده‌ی کلاس سرویس AuthInterceptor، سرویس Auth تزریق شده‌است که این سرویس نیز دارای HttpClient تزریق شده‌ی در سازنده‌ی آن است. به همین جهت Angular تصور می‌کند که ممکن است در اینجا یک بازگشت بی‌نهایت بین این interceptor و سرویس Auth رخ‌دهد. اما از آنجائیکه ما هیچکدام از متدهایی را که با HttpClient کار می‌کنند، در اینجا فراخوانی نمی‌کنیم و تنها کاربرد سرویس Auth، دریافت توکن دسترسی است، این مشکل را می‌توان به صورت ذیل برطرف کرد:
import { Injector } from "@angular/core";

  constructor(private injector: Injector) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
ابتدا سرویس Injector را به سازنده‌ی کلاس AuthInterceptor تزریق می‌کنیم و سپس توسط متد get آن، سرویس Auth را درخواست خواهیم کرد (بجای تزریق مستقیم آن در سازنده‌ی کلاس):
@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private injector: Injector) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
    const accessToken = authService.getRawAuthToken(AuthTokenType.AccessToken);
    if (accessToken) {
      request = request.clone({
        headers: request.headers.set("Authorization", `Bearer ${accessToken}`)
      });
    }

    return next.handle(request);
  }
}


تکمیل متدهای دریافت اطلاعات از کنترلرهای محافظت شده‌ی سمت سرور

اکنون پس از افزودن AuthInterceptor، می‌توان متدهای CallProtectedApiComponent را به صورت ذیل تکمیل کرد. ابتدا سرویس‌های Auth ،HttpClient و همچنین تنظیمات آغازین برنامه را به سازنده‌ی CallProtectedApiComponent تزریق می‌کنیم:
  constructor(
    private authService: AuthService,
    private httpClient: HttpClient,
    @Inject(APP_CONFIG) private appConfig: IAppConfig,
  ) { }
سپس متدهای httpClient.get و یا هر نوع متد مشابه دیگری را به صورت معمولی فراخوانی خواهیم کرد:
  callMyProtectedAdminApiController() {
    this.httpClient
      .get(`${this.appConfig.apiEndpoint}/MyProtectedAdminApi`)
      .map(response => response || {})
      .catch((error: HttpErrorResponse) => Observable.throw(error))
      .subscribe(result => {
        this.result = result;
      });
  }

  callMyProtectedApiController() {
    this.httpClient
      .get(`${this.appConfig.apiEndpoint}/MyProtectedApi`)
      .map(response => response || {})
      .catch((error: HttpErrorResponse) => Observable.throw(error))
      .subscribe(result => {
        this.result = result;
      });
  }

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



مدیریت خودکار خطاهای عدم دسترسی ارسال شده‌ی از سمت سرور

ممکن است کاربری درخواستی را به منبع محافظت شده‌ای ارسال کند که به آن دسترسی ندارد. در AuthInterceptor تعریف شده می‌توان به وضعیت این خطا، دسترسی یافت و سپس کاربر را به صفحه‌ی accessDenied که در قسمت قبل ایجاد کردیم، به صورت خودکار هدایت کرد:
    return next.handle(request)
      .catch((error: any, caught: Observable<HttpEvent<any>>) => {
        if (error.status === 401 || error.status === 403) {
          this.router.navigate(["/accessDenied"]);
        }
        return Observable.throw(error);
      });
در اینجا ابتدا نیاز است سرویس Router، به سازنده‌ی کلاس تزریق شود و سپس متد catch درخواست پردازش شده، به صورت فوق جهت عکس العمل نشان دادن به وضعیت‌های 401 و یا 403 و هدایت کاربر به مسیر accessDenied تغییر کند:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(
    private injector: Injector,
    private router: Router) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
    const accessToken = authService.getRawAuthToken(AuthTokenType.AccessToken);
    if (accessToken) {
      request = request.clone({
        headers: request.headers.set("Authorization", `Bearer ${accessToken}`)
      });
      return next.handle(request)
        .catch((error: any, caught: Observable<HttpEvent<any>>) => {
          if (error.status === 401 || error.status === 403) {
            this.router.navigate(["/accessDenied"]);
          }
          return Observable.throw(error);
        });
    } else {
      // login page
      return next.handle(request);
    }
  }
}
برای آزمایش آن، یک کنترلر سمت سرور جدید را با نقش Editor اضافه می‌کنیم:
using ASPNETCore2JwtAuthentication.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

namespace ASPNETCore2JwtAuthentication.WebApp.Controllers
{
    [Route("api/[controller]")]
    [EnableCors("CorsPolicy")]
    [Authorize(Policy = CustomRoles.Editor)]
    public class MyProtectedEditorsApiController : Controller
    {
        public IActionResult Get()
        {
            return Ok(new
            {
                Id = 1,
                Title = "Hello from My Protected Editors Controller! [Authorize(Policy = CustomRoles.Editor)]",
                Username = this.User.Identity.Name
            });
        }
    }
}
و برای فراخوانی سمت کلاینت آن در CallProtectedApiComponent خواهیم داشت:
  callMyProtectedEditorsApiController() {
    this.httpClient
      .get(`${this.appConfig.apiEndpoint}/MyProtectedEditorsApi`)
      .map(response => response || {})
      .catch((error: HttpErrorResponse) => Observable.throw(error))
      .subscribe(result => {
        this.result = result;
      });
  }
چون این نقش جدید به کاربر جاری انتساب داده نشده‌است (جزو اطلاعات سمت سرور او نیست)، اگر آن‌را توسط متد فوق فراخوانی کند، خطای 403 را دریافت کرده و به صورت خودکار به مسیر accessDenied هدایت می‌شود:



نکته‌ی مهم: نیاز به دائمی کردن کلیدهای رمزنگاری سمت سرور

اگر برنامه‌ی سمت سرور ما که توکن‌ها را اعتبارسنجی می‌کند، ری‌استارت شود، چون قسمتی از کلیدهای رمزگشایی اطلاعات آن با اینکار مجددا تولید خواهند شد، حتی با فرض لاگین بودن شخص در سمت کلاینت، توکن‌های فعلی او برگشت خواهند خورد و از مرحله‌ی تعیین اعتبار رد نمی‌شوند. در این حالت کاربر خطای 401 را دریافت می‌کند. بنابراین پیاده سازی مطلب «غیرمعتبر شدن کوکی‌های برنامه‌های ASP.NET Core هاست شده‌ی در IIS پس از ری‌استارت آن» را فراموش نکنید.



کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود. 
  • #
    ‫۶ سال و ۸ ماه قبل، چهارشنبه ۱۳ دی ۱۳۹۶، ساعت ۱۷:۳۸
    یک نکته‌ی تکمیلی: بهبود کنترل نمایش و مخفی سازی قسمت‌های مختلف

    یک روش «نمایش و یا مخفی کردن قسمت‌های مختلف صفحه بر اساس نقش‌های کاربر وارد شده‌ی به سیستم» را در مطلب جاری مطالعه کردید. روش دیگر اینکار، تهیه‌ی یک دایرکتیو و سپس اعمال آن به المان‌های مختلف صفحه است. به علاوه با توجه به اینکه Auth Service ما رخ‌داد خروج کاربر را نیز گزارش می‌کند، روش ارائه شده‌ی در اینجا نیاز به اندکی بهبود هم دارد:
      ngOnInit() {
        this.isAdmin = this.authService.isAuthUserInRole("Admin");
        this.isUser = this.authService.isAuthUserInRole("User");
      }
    نتیجه‌ی این بررسی، حتی با خروج کاربر نیز تغییری نخواهد کرد و ثابت است. بنابراین بهتر است مشترک this.authService.authStatus شد و نسبت به رخ‌دادهای صادر شده‌ی توسط سرویس اعتبارسنجی، همانند کامپوننت هدر، واکنش نشان داد.
    برای پیاده سازی آن و همچنین کپسوله سازی این عملیات تکراری، دایرکتیو جدیدی را در مسیر src\app\shared\directives\is-visible-for-auth-user.directive.ts ایجاد می‌کنیم:
    import { Directive, ElementRef, Input, OnDestroy, OnInit } from "@angular/core";
    import { Subscription } from "rxjs/Subscription";
    
    import { AuthService } from "../../core/services/auth.service";
    
    @Directive({
      selector: "[isVisibleForAuthUser]"
    })
    export class IsVisibleForAuthUserDirective implements OnInit, OnDestroy {
    
      private subscription: Subscription;
    
      @Input() isVisibleForRoles: string[];
    
      constructor(private elem: ElementRef, private authService: AuthService) { }
    
      ngOnDestroy(): void {
        this.subscription.unsubscribe();
      }
    
      ngOnInit(): void {
        this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status));
        this.changeVisibility(this.authService.isAuthUserLoggedIn());
      }
    
      private changeVisibility(status: boolean) {
        const isInRoles = !this.isVisibleForRoles ? true : this.authService.isAuthUserInRoles(this.isVisibleForRoles);
        this.elem.nativeElement.style.display = isInRoles && status ? "" : "none";
      }
    }
    در اینجا علاوه بر بررسی isAuthUserLoggedIn و isAuthUserInRoles، نسبت به تغییرات this.authService.authStatus نیز واکنش نشان داده می‌شود.

    سپس تعریف آن‌را به قسمت‌های declarations و exports مربوط به SharedModule اضافه می‌کنیم:
    import { IsVisibleForAuthUserDirective } from "./directives/is-visible-for-auth-user.directive";
    
    @NgModule({
      declarations: [
        IsVisibleForAuthUserDirective
      ],
      exports: [
        IsVisibleForAuthUserDirective
      ]
    })
    export class SharedModule {}

    اکنون ماژول Dashboard برای استفاده‌ی از این امکانات تنها کافی است SharedModule را دریافت کند (یا هر ماژول دیگری نیز به همین ترتیب است):
    import { SharedModule } from "../shared/shared.module";
    
    @NgModule({
      imports: [
        SharedModule
      ]
    })
    export class DashboardModule { }

    پس از آن برای مخفی سازی یک المان از دید کاربران وارد نشده‌ی به سیستم، فقط کافی است دایرکتیو isVisibleForAuthUser را به المان اعمال کنیم:
    <div class="alert alert-info" isVisibleForAuthUser>
          Is-Visible-For-AuthUser
    </div>
    و یا اگر نیاز به اعمال نقش‌ها نیز وجود داشت می‌توان از خاصیت isVisibleForRoles آن استفاده کرد:
    <div class="alert alert-success" isVisibleForAuthUser [isVisibleForRoles]="['Admin','User']">
          Is-Visible-For-Roles = ['Admin','User']
    </div>

    خلاصه‌ی این تغییرات به کدهای نهایی این سری اعمال شده‌اند.
  • #
    ‫۶ سال و ۷ ماه قبل، سه‌شنبه ۲۴ بهمن ۱۳۹۶، ساعت ۱۷:۳۳
    به روز رسانی
    جهت حذف خطای «cyclic dependency» که در متن عنوان شد و همچنین کاهش مسئولیت‌های کلاس سرویس Auth، دو سرویس جدید token-store.service.ts (برای ذخیره و بازیابی توکن‌های دریافتی از سرور) و refresh-token.service.ts (مدیریت به روز رسانی خودکار توکن) اضافه و در اصل از auth.service.ts استخراج شدند. به این ترتیب auth.interceptor.ts دیگر نیازی به this.injector.get ندارد و تزریق مستقیم در سازنده‌ی آن کار می‌کند.
  • #
    ‫۶ سال و ۶ ماه قبل، یکشنبه ۶ اسفند ۱۳۹۶، ساعت ۱۴:۴۱
    یک نکته‌ی تکمیلی: روش دیگری برای بهبود کنترل نمایش و مخفی سازی قسمت‌های مختلف صفحه

    کدهای یک دایرکتیو سفارشی نمایش و یا مخفی سازی قسمت‌های مختلف صفحه را بر اساس سطوح دسترسی کاربر جاری، در IsVisibleForAuthUserDirective مشاهده کردید. روش دیگر انجام اینکار، نوشتن یک دایرکتیو ساختاری شبیه به ngIf توکار خود Angular است. کاری که ngIf انجام می‌دهد، مخفی کردن یک المان در صفحه نیست؛ بلکه کل آن‌را از DOM حذف می‌کند.

    نکته‌ی اصلی پیاده سازی یک دایرکتیو ساختاری

    اگر به سازنده‌ی IsVisibleForAuthUserDirective دقت کنید، تزریق وابستگی ElementRef را داریم:
    @Directive({
      selector: "[isVisibleForAuthUser]"
    })
    export class IsVisibleForAuthUserDirective implements OnInit, OnDestroy {
      constructor(private elem: ElementRef, private authService: AuthService) { }
    به این ترتیب Angular به صورت خودکار امکان دسترسی به المان جاری را با تزریق آن در سازنده‌ی کلاس، میسر می‌کند.
    برای ایجاد یک دایرکتیور ساختاری، نیاز است از تزریق TemplateRef و ViewContainerRef استفاده کرد:
    @Directive({
    selector: "[hasAuthUserViewPermission]"
    })
    export class HasAuthUserViewPermissionDirective implements OnInit, OnDestroy {
    
      constructor(
      private templateRef: TemplateRef<any>,
      private viewContainer: ViewContainerRef,
      private authService: AuthService
    ) { }
    در این حالت Angular تنها زمانی این وابستگی‌ها را به سازنده‌ی کلاس تزریق می‌کند که پیش از نام این دایرکتیو، یک * قرار دهید (مانند ngIf توکار آن):
    <div *hasAuthUserViewPermission="['Admin','User']">
        *hasAuthUserViewPermission="['Admin','User']"
    </div>
    پس از آن می‌توان از viewContainer، برای نمایش المان و یا قالب مدنظر:
     this.viewContainer.createEmbeddedView(this.templateRef);
    و یا حذف کامل آن المان از DOM کمک گرفت:
     this.viewContainer.clear();

    اگر این نکات را کنار هم قرار دهیم و کدهای IsVisibleForAuthUserDirective فوق را بر این اساس اصلاح کنیم، به قطعه کد زیر می‌رسیم:
    import { Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from "@angular/core";
    import { Subscription } from "rxjs/Subscription";
    
    import { AuthService } from "./../../core/services/auth.service";
    
    @Directive({
      selector: "[hasAuthUserViewPermission]"
    })
    export class HasAuthUserViewPermissionDirective implements OnInit, OnDestroy {
      private isVisible = false;
      private requiredRoles: string[] | null = null;
      private subscription: Subscription | null = null;
    
      @Input()
      set hasAuthUserViewPermission(roles: string[] | null) {
        this.requiredRoles = roles;
      }
    
      // Note, if you don't place the * in front, you won't be able to inject the TemplateRef<any> or ViewContainerRef into your directive.
      constructor(
        private templateRef: TemplateRef<any>,
        private viewContainer: ViewContainerRef,
        private authService: AuthService
      ) { }
    
      ngOnInit() {
        this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status));
        this.changeVisibility(this.authService.isAuthUserLoggedIn());
      }
    
      ngOnDestroy(): void {
        if (this.subscription) {
          this.subscription.unsubscribe();
        }
      }
    
      private changeVisibility(status: boolean) {
        const isInRoles = !this.requiredRoles ? true : this.authService.isAuthUserInRoles(this.requiredRoles);
        if (isInRoles && status) {
          if (!this.isVisible) {
            this.viewContainer.createEmbeddedView(this.templateRef);
            this.isVisible = true;
          }
        } else {
          this.isVisible = false;
          this.viewContainer.clear();
        }
      }
    }

    سپس تعریف آن‌را به قسمت‌های declarations و exports مربوط به SharedModule اضافه می‌کنیم:
    import { HasAuthUserViewPermissionDirective } from "./directives/has-auth-user-view-permission.directive";
    
    @NgModule({
      declarations: [
        HasAuthUserViewPermissionDirective
      ],
      exports: [
        HasAuthUserViewPermissionDirective
      ]
    })
    export class SharedModule {}

    اکنون ماژول Dashboard برای استفاده‌ی از این امکانات تنها کافی است SharedModule را دریافت کند (یا هر ماژول دیگری نیز به همین ترتیب است):
    import { SharedModule } from "../shared/shared.module";
    @NgModule({
      imports: [
        SharedModule
      ]
    })
    export class DashboardModule { }

    پس از آن برای مخفی سازی یک المان از دید کاربران وارد نشده‌ی به سیستم، فقط کافی است دایرکتیو ساختاری hasAuthUserViewPermission را به المان اعمال کنیم:
    <div *hasAuthUserViewPermission="">
        *hasAuthUserViewPermission=""
    </div>
    و یا اگر نیاز به اعمال نقش‌ها نیز وجود داشت می‌توان به صورت زیر عمل کرد:
    <div *hasAuthUserViewPermission="['Admin','User']">
        *hasAuthUserViewPermission="['Admin','User']"
    </div>

    خلاصه‌ی این تغییرات به کدهای نهایی این سری اعمال شده‌اند.
    • #
      ‫۶ سال و ۴ ماه قبل، سه‌شنبه ۱۱ اردیبهشت ۱۳۹۷، ساعت ۱۸:۳۸
      از این روش برای مخفی کردن و نمایش منوهای برنامه استفاده میکنم که کامپوننت منوهای برنامه هم بصورت جداگانه ایجاد و در قالب اصلی برنامه لود میشه.اما زمانیکه متد
      this.authStatusSource.next(true);
      که در متد refreshToken قراردارد را فراخوانی میکنم متد:
      this.authService.authStatus$.subscribe(status => this.changeVisibility(status));
      اجرا نمیشه!
  • #
    ‫۵ سال و ۸ ماه قبل، دوشنبه ۳ دی ۱۳۹۷، ساعت ۱۳:۴۳
    چگونه می‌توانم افراد انلاین به سیستم رو نمایش بدم؟
  • #
    ‫۵ سال و ۷ ماه قبل، چهارشنبه ۱۷ بهمن ۱۳۹۷، ساعت ۱۲:۳۱
    زمانیکه از متد http به جای  httpClient برای استفاده از داده‌های محافظت شده سمت سرور استفاده میکنم اجازه دسترسی به آنها را نمیدهد و در هدر ارسال شده این پیام رو نشان میدهد:   
    Request URL: http://localhost:5000/api/office?page=1&pageSize=5
    Request Method: GET
    Status Code: 401 Unauthorized
    Remote Address: [::1]:5000
    Referrer Policy: no-referrer-when-downgrade
    • #
      ‫۵ سال و ۷ ماه قبل، چهارشنبه ۱۷ بهمن ۱۳۹۷، ساعت ۱۲:۴۰
      Http Module منسوخ شده‌است (و زمانیکه ماژولی منسوخ شده اعلام می‌شود، یعنی در چند نگارش بعد، از کل مجموعه حذف خواهد شد). این سری از Http Client استفاده می‌کند که مطلب روش «ارتقاء به HTTP Client در Angular 4.3» را در این مورد می‌توانید مطالعه کنید؛ به همراه مطلب «ارتقاء به Angular 6: بررسی تغییرات RxJS». مباحث Interceptors توکار مورد استفاده‌ی در اینجا، برای Http Client آن وجود خارجی دارد. برای Http Module منسوخ شده‌، روش «راه‌اندازی Http Interceptor در Angular» وجود داشت.
      • #
        ‫۵ سال و ۷ ماه قبل، چهارشنبه ۱۷ بهمن ۱۳۹۷، ساعت ۱۷:۱۸
        مطالبی که توضیح دادید رو مطالعه کردم و استفاده کردم
        فقط در حالت استفاده از httpClient  وقتی از متدهای put،delete و post استفاده میکنم این خطا رو میده:
        Request URL: http://localhost:5000/api/ChangePassword
        Request Method: POST
        Status Code: 400 Bad Request
        Remote Address: [::1]:5000
        Referrer Policy: no-referrer-when-downgrade
        این نمونه post از همون پروژه خودتون هست
  • #
    ‫۳ سال و ۵ ماه قبل، جمعه ۲۲ اسفند ۱۳۹۹، ساعت ۱۵:۱۰
    جایگاه کتابخانه‌های ثالث(third party lib) همانند ngx bootstrap (برای استفاده از امکانات بوت استرپ بدون استفاده از جی کوئری)در این ساختار به چه شکل خواهد بود. آیا باید همگی در root یا همان app.module نصب و فعالسازی گردد برای استفاده در بقیه جاهای برنامه یا بهتره که به ماژول خاصی منتقل گردند.