Access Control ด้วย Route Guard ใน Angular

ใน App ทั่ว ๆ ไปที่ต้องมีการ login ก่อนการใช้งาน บาง route ก็จะเป็น route ที่ใครก็สามารถเข้าไปใช้งานได้ แต่บาง route อาจจะไม่ ในกรณีที่เราอยากให้บาง route ที่เรากำหนดขึ้นมาสามารถเข้าถึงได้ด้วย user แค่บางกลุ่ม เช่น admin หรือเฉพาะเจ้าของ route นั้น เราสามารถทำได้ด้วยการใช้ Route Guard

Route Guard

ถ้าเทียบกันระหว่าง Angular กับ AngularJS ตัว Route Guard น่าจะเป็นอะไรที่เหมือนกับการใช้ Resolve ใน AngularJS แต่เพียงแค่ว่า Route Guard จะแยกตัวที่สำคัญออกมาให้ใช้งานโดยเฉพาะเลย ส่วนที่เหลือก็ไปใช้ใน Resolve เหมือนเดิม

Route Guard จะถูกเรียก เพื่อ resolve ดูว่ามันมีค่าเป็น true หรือ false เท่านั้น ถ้าเราต้องการให้ guard เป็น true ก็แค่ return true หรือถ้าอยากให้เป็น false ก็แค่ return false เท่านั้น โดยที่ guard ทั้งหมดจะมีอยู่ 5 แบบคือ

  • CanActivate เอาไว้ใช้ดูว่า route ที่พยายามจะเข้าถึงนั้น ได้รับการอนุญาตให้เข้าถึงหรือไม่
  • CanActivateChild เอาไว้ดูว่า child root ที่พยายามจะเข้าถึงนั้น ได้รับการอนุญาตให้เข้าถึงหรือไม่
  • CanDeactivate เอาไว้ดูว่า route ที่เราเข้าใช้งานอยู่นั้น ได้รับการอนุญาตให้ route ไปที่อื่นต่อหรือไม่
  • CanLoad เอาไว้ดูว่า route ที่พยายามจะเข้าถึงนั้น อนุญาตให้โหลด feature module แบบ asynchronous หรือไม่
  • Resolve เอาไว้ resolve ค่าอื่น ๆ ก่อนที่จะทำการ navigate ไปที่ route ที่กำหนด

วิธีใช้งานก็แค่เอาไปใส่เป็น option หนึ่งใน router และลำดับในการเช็คจะเป็น CanDectivate, CanActivateChild ก่อนจาก child route ที่อยู่ลึกสุดมาหา route ที่อยู่นอกสุด เสร็จแล้วจะเช็ค CanActivate จาก route นอกสุดไป child route ในสุด ถ้ามี feature module ถูกโหลดแบบ asynchronous ตัว guard CanLoad ก็จะถูกเช็ค และถ้า guard ตัวใดตัวหนึ่ง return false การ resolve guard ที่เหลือจะถูกยกเลิก และการ navigate นั้น ๆ ก็จะถูกยกเลิกไปด้วย

ในเว็บ Angular เอง มีบอกไว้ว่าตัว CanActivate guard มันเอาไว้สำหรับจัดการเรื่องการเข้าถึง route แบบ route นี้มี Authentication นะ ฉันอนุญาตให้ใครเข้าได้ใครเข้าไม่ได้บ้างได้เลย

เริ่มจาก สร้างหน้าที่เป็นหน้าที่เรากะว่าไม่ให้ใครเข้าหน้านี้ได้ ถ้าไม่ได้รับอนุญาต ตั้งชื่อว่า RestrictedComponent เข้าได้ผ่าน path /restricted

import { RouterModule } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { RestrictedComponent } from './components/restricted/restricted.component';

@NgModule({
  declarations: [
    AppComponent,
    RestrictedComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      {
        path: '/restricted',
        component: RestrictedComponent
      }
    ])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

อย่าลืมไปแก้ app.component.html ให้มี router-outlet component ด้วย

<router-outlet></router-outlet>

ลองเปิด browser ไปที่ path /restricted ดูว่าเป็นไปตามที่ควรจะเป็น

ดูดีเลยทีเดียว ^_^

ถัดมาก็สร้าง Service ที่เอาไว้ใช้เช็คก่อนว่าอนุญาตหรือไม่อนุญาตให้เข้าใช้งาน ตั้งชื่อหล่อ ๆ ว่า AuthGuardService

ng g service services/auth-guard

สิ่งที่ต้องทำก็แค่ implements จาก interface CanActivate โดยใส่ method implementation canActivate() ที่เราต้องการ ดังนั้นลองใส่ไปแค่นี้ดู

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';

@Injectable()
export class AuthGuardService implements CanActivate {

  constructor() { }

  canActivate(): boolean {
    console.log('AuthGuard.canActivate is called');
    return true;
  }

}

เสร็จแล้วเอาไปผูกกับ route /restricted เดิม

import { RouterModule } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AuthGuardService } from './services/auth-guard.service';
import { RestrictedComponent } from './components/restricted/restricted.component';

@NgModule({
  declarations: [
    AppComponent,
    RestrictedComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      {
        path: 'restricted',
        component: RestrictedComponent,
        canActivate: [AuthGuardService]
      }
    ])
  ],
  providers: [AuthGuardService],
  bootstrap: [AppComponent]
})
export class AppModule { }

กลับไปที่ browser ลอง browse ไปที่ /restricted อีกที จะเห็นว่า ยังโชว์ว่า restricted works! อยู่ ลองเปิด console ก็จะเห็นว่ามันแสดงว่า AuthGuard.canActivate is called ตามที่เราสั่งไว้ใน service

ถ้าจะให้มันไม่ผ่านก็แค่ return false เท่านั้นเอง ลองใส่ return false แล้วกลับไปที่ /restricted อีกที จะเห็นว่ามัน redirect กลับไปที่ / ดังนั้น ถ้าเราอยากให้มัน redirect ไปหน้าที่ต้องการ ก็แค่สั่ง navigate ไปที่ต้องการก่อน return false เท่านั้นเอง

เราลองสร้าง component login แล้วไปผูกกับ path /login ดู เสร็จแล้วแก้ AuthGuardService เป็นแบบนี้

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';

@Injectable()
export class AuthGuardService implements CanActivate {

  constructor(private router: Router) { }

  canActivate(): boolean {
    console.log('AuthGuard.canActivate is called');
    this.router.navigate(['/login']);
    return false;
  }

}

ทีนี้ลองพยายามเข้า /restricted มันจะ redirect ไปที่ /login เสมอ

ดังนั้นที่เหลือก็แค่ใส่ logic เช็คดูว่าไอ้คนนี้มัน login หรือยัง ถ้า login แล้ว มี permission ตามที่เราอนุญาตหรือไม่ ถ้าทุกอย่าง ok ก็ return true ถ้าไม่ ก็ return false แค่นั้นเอง

ส่วนวิธี authen จะใช้แบบไหนก็แล้วแต่เลย เลือกแบบที่ใช้แล้วสบายใจ JWT ก็ดีนะ 🙂

โค้ดตัวอย่าง

อยู่นี่ https://github.com/chonla/ng-route-guard-example

ดูเพิ่มเติม

  • https://angular.io/guide/router
  • https://medium.com/@ryanchenkie_40935/angular-authentication-using-route-guards-bf7a4ca13ae3

Leave a Reply