مقالات طراحی سایت

آموزش موارد مربوط به طراحی و ساخت وب سایت

انگولار برای Authentication فیچرهایی با نام route guard دارد. در این مقاله این ویژگی ها را بررسی کرده و نحوه استفاده از آنها را در اپلیکیشن انگولار توضیح میدهدم.

Route Guard

Route Guardها اینترفیس هایی هستند که اجازه نویگیشن به route درخواست شده را برای router صادر میکند. Route Guardها این کار را با مقدار بازگشتی true یا false کلاسی که اینترفیس guard برای آن پیاده سازی شده است انجام میدهد.

پنج نوع guard وجود دارد که هر یک از آنها براساس نیاز فراخوانی میشوند. رفتار router براساس guardی که استفاده شده است تفاوت دارد. انواع guardها بصورت زیر هستند:

  • CanActivate
  • CanActivateChild
  • CanDeactivate
  • CanLoad
  • Resolve

در این مقاله خیلی وارد جزئیات هر guard نمیشویم. برای اطلاعات بیشتر میتوانید به سایت انگولار مراجعه کنید.

تصمیمات routing براساس منقضی شدن token

اگر برای امنیت اپ خود از JSON Web Tokens یا به اختصار JWT استفاده میکنید، یکی از راههایی تصمیم گریری درباره نمایش route، زمان انقضای token است.

برای اینکار، سرویس authentication ایجاد کنید و در آن سرویس متدی ایجاد کنید که کاربران تایید شده را از طریق token چک کند. برای این کار، کلاس JwtHelperService از angular2-jwt میتواند مورد استفاده قرار گیرد.

 
npm install --save @auth0/angular-jwt
 

از angular-jwt در AuthService استفاده کنید.

 
// src/app/auth/auth.service.ts
import { Injectable } from '@angular/core';
import { JwtHelperService } from '@auth0/angular-jwt';
@Injectable()
export class AuthService {
 constructor(public jwtHelper: JwtHelperService) {}
 // ...
 public isAuthenticated(): boolean {
 const token = localStorage.getItem('token');
 // Check whether the token is expired and return
 // true or false
 return !this.jwtHelper.isTokenExpired(token);
 }
}
 

فرض ما بر این است که شما JWT کاربر را در local storage ذخیره میکنید.

سرویس تازه ای ایجاد کنید که route guard را پیاده سازی میکند. شما میتوانید این سرویس را هرجایی که بخواهید پیاده سازی کنید، اما بهتر است در فایل auth-guard.service پیاده سازی کنید.

 
// src/app/auth/auth-guard.service.ts
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuardService implements CanActivate {
 constructor(public auth: AuthService, public router: Router) {}
 canActivate(): boolean {
 if (!this.auth.isAuthenticated()) {
 this.router.navigate(['login']);
 return false;
 }
 return true;
 }
}
 

در این سرویس AuthService و router را inject میکنیم و متدی با نام canActivate ایجاد میکنیم. این متد برای پیاده سازی اینترفیس canActivate لازم است.

متد canActivate یک boolean بر میگرداند. مقدار boolean تعیین میکند navigation به route باید انجام شود یا نه. اگر کاربر تایید شده نباشد به مسیر دیگری هدایت میشود. در مثال بالا به /login هدایت میشود.

اکنون میتوانید این guard را برای هر route که بخواهید استفاده کنید.

 
// src/app/app.routes.ts
import { Routes, CanActivate } from '@angular/router';
import { ProfileComponent } from './profile/profile.component';
import { 
 AuthGuardService as AuthGuard 
} from './auth/auth-guard.service';
export const ROUTES: Routes = [
 { path: '', component: HomeComponent },
 { 
 path: 'profile',
 component: ProfileComponent,
 canActivate: [AuthGuard] 
 },
 { path: '**', redirectTo: '' }
];
 

در مثال بالا مسیر /profile تنظیمات canActivate دارد. هر بار که شخصی بخواهد به مسیر /profile دسترسی داشته باشد AuthGuard اجرا میشود. اگر کاربر تایید شده باشد، مسیر را میبیند. اما اگر تایید شده نباشد به مسیر /login هدایت میشود.

بررسی نقش کاربر

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

برای مثال، میخواهیم کاربرانی که نقش معینی دارند به route دسترسی داشته باشند. برای اینکار، میتوانیم guardی ایجاد کنیم که دنبال نقش های معینی در payload JWT کاربر باشد.

برای خواندن payload JWT لازم است jwt-decode را نصب کنید.

 
npm install --save jwt-decode
 

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

سرویس جدیدی به نام RoleGuardService کنید.

 
// src/app/auth/role-guard.service.ts
import { Injectable } from '@angular/core';
import { 
 Router,
 CanActivate,
 ActivatedRouteSnapshot
} from '@angular/router';
import { AuthService } from './auth.service';
import decode from 'jwt-decode';
@Injectable()
export class RoleGuardService implements CanActivate {
 constructor(public auth: AuthService, public router: Router) {}
 canActivate(route: ActivatedRouteSnapshot): boolean {
 // this will be passed from the route config
 // on the data property
 const expectedRole = route.data.expectedRole;
 const token = localStorage.getItem('token');
 // decode the token to get its payload
 const tokenPayload = decode(token);
 if (
 !this.auth.isAuthenticated() || 
 tokenPayload.role !== expectedRole
 ) {
 this.router.navigate(['login']);
 return false;
 }
 return true;
 }
}
 

در این guard ما از ActivatedRouteSnapshot استفاده کردیم تا به پراپرتی data برای route درخواست شده دسترسی پیدا کنیم. با کمک پراپرتی data میتوانیم از طریق تنظیمات route، آبجکتی با پراپرتی های کاستوم بفرستیم. بعدا میتوانیم برای تصمیم گیری route، از آن دیتای کاستوم استفاده کنیم.

در این مورد ما دنبال نقشی هستیم که کاربر برای دسترسی به route باید داشته باشد. ما token را دی کد میکنیم تا payload آنرا بدست آوریم. اگر کاربر تایید شده نباشد یا اگر نقشی که انتظار داشتیم را در payload token نداشته باشد، ما navigation را کنسل میکنیم و به صفحه ورود هدایتش میکنیم. در غیر اینصورت کاربر میتواند به کار خودشان ادامه دهد.

الان میتوانید این guard را برای هر route که بخواهید استفاده کنید. مثلا شاید بخواهیم از مسیر /admin محافظت کنیم.

 
// src/app/app.routes.ts
import { Routes, CanActivate } from '@angular/router';
import { ProfileComponent } from './profile/profile.component';
import { 
 AuthGuardService as AuthGuard 
} from './auth/auth-guard.service';
import { 
 RoleGuardService as RoleGuard 
} from './auth/role-guard.service';
export const ROUTES: Routes = [
 { path: '', component: HomeComponent },
 { 
 path: 'profile', 
 component: ProfileComponent, 
 canActivate: [AuthGuard] 
 },
 { 
 path: 'admin', 
 component: AdminComponent, 
 canActivate: [RoleGuard], 
 data: { 
 expectedRole: 'admin'
 } 
 },
 { path: '**', redirectTo: '' }
];
 

برای مسیر /admin، ما همچنان از canActivate برای کنترل navigation استفاده میکنیم، اما این بار یک آبجکت که expectedRole دارد را به پراپرتی data میفرستیم. ما expectedRole را در RoleGuardService تعریف کرده ایم.

فرض ما بر این است که شما از roleی که در JWT خود معرفی کرده اید استفاده میکنید.

آیا routeها نمیتوان هک کرد؟

احتمالا شما به این موضوع فکر میکنید که کاربر میتواند به راحتی مسیر محافظت شده را هک کند. هر کاربری که کمی دانش داشته باشد میتواند JWT خود را در local storage پیدا کند و به سایت jwt.io برود و بر اساس token خود payload را تغییر دهد. برای مثال، میتواند exp را تغییر بدهد تا زمان استفاده از token را افزایش دهد، یا حتی نقش متفاوت به خود اختصاص دهد.

این درست است، کاربر میتواند این کارها را انجام دهد و از guardها عبور کند. نکته اصلی این است که تغییر در payload token باعث نقض امضاء میشود. امضاء JSON Web Token با payload محاسبه میشود، بنابراین، ویرایش آن باعث از بین رفتن امضاء میشود. این خبر خوبی است زیرا کاربر نمیتواند به محتوای حفاظت شده شما دسترسی پیدا کند.

هر دیتای حساسی که نمیخواهید کاربران تایید نشده به آن دسترسی پیدا کنند باید در سرور ذخیره شود. با این کار، اگر کاربری بخواهد routeی را که قرار نیست به آن دسترسی داشته باشد ببیند، نمیتواند دیتای آن route را ببیند.

نمیشود route را بطور کلی مسدود کرد؟

در برخی مواقع، واقعا میخواهید route های سمت کاربر کاملا بسته شود. هرچند 100% امکان این کار وجود ندارد که از سمت کاربر routeها بسته شوند، اما انگولار امکانات جالبی از طریق async routing فراهم کرده است.

نتیجه گیری

استفاده از route guard انگولار به شما کمک میکند تا از routeهای سمت کاربر محافظت کنید. به یاد داشته باشید هیچ چیز در سمت کاربر کاملا محافظت شده نیست. هر کد، دیتا، یا ... که سمت مرورگر کاربر میفرستید در دسترس آنها قراردارد. با توجه به این موضوع، مطمئن شوید که اطلاعات حساس در بک اند محافظت میشود.

به نقل از: Medium

نوشتن دیدگاه