انگولار برای 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