Angular Authentication ด้วย ASP.NET Core และ JWT

บทความนี้จะแสดงการพัฒนาระบบ Authentication และ Authorisation ของ Angular โดยใช้ JWT ร่วมกับ ASP.NET Core Web API ลองทำตามกันดูนะครับ

บทความที่จำเป็นก่อนทำ

  1. แก้ไขไฟล์ app.module.js

เพิ่ม Import ดังนี้

import { HttpClientModule } from '@angular/common/http'

ที่ @NgModule เพิ่ม Imports - HttpClientModule ดังนี้

imports: [
  BrowserModule,
  HttpClientModule
],
  1. เปิด Terminal หรือ Command Prompt เข้าไปที่ Project Directory และเข้าไปที่ ClientApp แล้วพิมพ์คำสั่ง

ใช้ Angular สร้าง Routing Module

ng g module app-routing --flat --module=app

ใช้ Angular สร้าง UserService

ng g service user

ใช้ Angular สร้าง ProductService

ng g service product

ใช้ Angular สร้าง Product Class

ng g class product

ใช้ Angular สร้าง Autherize Guard

ng g guard auth

ใช้ Angular สร้าง Register Component

ng g component register

ใช้ Angular สร้าง Login Component

ng g component login

ใช้ Angular สร้าง Product Component

ng g component product

3.แก้ไขไฟล์ user.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';

const headers = new HttpHeaders().set('Content-Type', 'application/json'); 

@Injectable({
  providedIn: 'root'
})
export class UserService {

  private isLoggedIn = false;
  private authNavStatusSource = new BehaviorSubject<boolean>(false);

  constructor(private http: HttpClient) { 
    this.isLoggedIn = !!localStorage.getItem('token');
    this.authNavStatusSource.next(this.isLoggedIn);
  }

  register(email: string, password: string): Observable<string> {      
    let body = JSON.stringify({email, password});
    return this.http.post("api/account/register", body, { responseType: 'text', headers });    
  }

  login(email: string, password: string): Observable<string> { 
    let body = JSON.stringify({email, password});
    var result = this.http.post("api/account/login", body, { responseType: 'text', headers });    
    result.subscribe(token => {
      localStorage.setItem('token', token);
      this.isLoggedIn = true;
      this.authNavStatusSource.next(true);
    });
    return result;
  }

  logout() {
    localStorage.removeItem('token');
    this.isLoggedIn = false;
    this.authNavStatusSource.next(false);
  }

  checkLoggedIn() {
    return this.isLoggedIn;
  }
}
  1. แก้ไชไฟล์ auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { UserService } from "./user.service";

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(private userService: UserService, private router: Router) { }

  canActivate() {
    if (!this.userService.checkLoggedIn())
    {
      this.router.navigate(['/login']);
      return false;
    }
    else return true;
  }
}
  1. แก้ไขไฟล์ app-routing.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { RegisterComponent } from "./register/register.component";
import { LoginComponent } from "./login/login.component";
import { ProductComponent } from "./product/product.component";
import { AuthGuard } from './auth.guard';

const routes: Routes = [
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  { path: 'register', component: RegisterComponent },
  { path: 'product', component: ProductComponent, canActivate: [AuthGuard] },
  { path: '**', component: LoginComponent }
];

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forRoot(routes)
  ],
  exports: [
    RouterModule
  ],
  declarations: []
})
export class AppRoutingModule { }

6.แก้ไขหน้า app.component.ts

import { Component } from '@angular/core';
import { UserService } from "./user.service";
import { Router } from "@angular/router";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  constructor(private userService: UserService, private router: Router) { }

  onLogout() {
    this.userService.logout();
    this.router.navigate(['/login']);
  }
}
  1. แก้ไขหน้า app.component.html
<h1>Code Bangkok</h1>
<nav>
    <a [routerLink]="['/login']">Login</a> | 
    <a [routerLink]="['/register']">Register</a> |
    <a [routerLink]="['/product']">Product</a> |
    <input type="submit" value="Logout" (click)="onLogout()"/>
</nav>
<router-outlet></router-outlet>
  1. แก้ไขไฟล์ register.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from '../user.service';
import { FormGroup, FormControl } from '@angular/forms';
import { Router } from "@angular/router";

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
  token: string;
  form: FormGroup;

  constructor(private userService: UserService, private router: Router) { }  

  ngOnInit() {
    this.form = new FormGroup({
      email: new FormControl(),
      password: new FormControl()
    });
  }

  onSubmit() {
    this.userService
      .register(this.form.controls.email.value, this.form.controls.password.value)
      .subscribe(result  => {
        if(result) {
          this.router.navigate(['/login']);
        };        
      });
  }
}
  1. แก้ไขไฟล์ register.component.html
<h2>Register</h2>
<div>
  <form [formGroup]="form" (ngSubmit)="onSubmit()">
    <div>
      Email: <input type="email" formControlName="email" placeholder="Email">            
    </div>
    <div>
      Password: <input type="password" formControlName="password" placeholder="Password">
    </div>   
    <div>
      <button type="submit">Sign Up</button>              
    </div>
  </form>
</div>
  1. แก้ไขไฟล์ login.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from '../user.service';
import { FormGroup, FormControl } from '@angular/forms';
import { Router } from "@angular/router";

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  token: string;
  form: FormGroup;

  constructor(private userService: UserService, private router: Router) { }  

  ngOnInit() {
    this.form = new FormGroup({
      email: new FormControl(),
      password: new FormControl()
    });    
  }
  onSubmit() {
    this.userService
      .login(this.form.controls.email.value, this.form.controls.password.value)
      .subscribe(result => {
        if (result) {
          this.router.navigate(['/product']);
        }
      });
  }
}
  1. แก้ไขไฟล์ login.component.html
<h2>Login</h2>
<div>
  <form [formGroup]="form" (ngSubmit)="onSubmit()" >
    <div>
      Email: <input type="email" formControlName="email" placeholder="Email">            
    </div>
    <div>
      Password: <input type="password" formControlName="password" placeholder="Password">
    </div>   
    <div>
      <button type="submit">Sign In</button>              
    </div>
  </form>
</div>
  1. แก้ไขไฟล์ product.ts
export class Product {
    id: number;
    name: string;
}

13.แก้ไขไฟล์ product.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Product } from './product';

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  constructor(private http: HttpClient) { }

  getData(): Observable<Product[]> {    
    let token = localStorage.getItem('token');
    let headers = new HttpHeaders()
      .set('Content-Type', 'application/json')
      .append('Authorization', `Bearer ${token}`); 
    return this.http.get<Product[]>("api/account/getdata", { headers });
  }
}
  1. แก้ไขไฟล์ product.component.ts
import { Component, OnInit } from '@angular/core';
import { ProductService } from '../product.service';
import { Product } from '../product';

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
  products: Product[];

  constructor(private service: ProductService) { }

  ngOnInit() {
    this.service.getData().subscribe(p => this.products = p);
  }
}
  1. แก้ไขไฟล์ product.component.html
<table border="1">
  <thead>
    <tr>
      <td>Id</td>
      <td>Name</td>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let product of products">
      <td>{{product.id}}</td>
      <td>{{product.name}}</td>
    </tr>
  </tbody>
</table>
  1. ทดสอบรันเวปด้วยคำสั่ง
dotnet watch run

เปิด Browser ไปที่ https://localhost:5001 จะ Redirect ไปที่ /login และจะเช้าไปที่ /product ไม่ได้
2018-06-11_15-00-03

เข้าไปหน้า Register เพื่อลงทะเบียน แล้วจะ Redirect ไปที่หน้า Login อีกครั้ง
2018-06-11_15-00-38

Login เข้าสู่ระบบ แล้วจะเปลี่ยนไปที่หน้า /product
2018-06-11_15-01-10

แสดงผลข้อมูลที่ดึงจาก ASP.NET Core Web API Authentication ด้วย JWT
2018-06-11_15-04-57

ที่มา: https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login

Source Code: https://github.com/CodeBangkok/CBAuthenJwt

ติดตามบทความเกี่ยวกับ Angular ต่อไปได้ที่ CodeBangkok นะครับ


บริจาค (Donate) ด้วย Crypto Currency ให้กับ CodeBangkok ได้ที่
BTC = 3GDxhb84ho2jmAV9seAgAFJ7dy1XR3GCyc
ETH = 0x119fa8A618A0283D1834853325A8FF4fe1101230
LTC = ME2abSdDeQYuTmzZSAnHL7LGGeF836d1ut
ZEC = t1Y4NkK3Dx3yBbwCVdpXzKYrqUSJSHgaFXa

https://www.lazada.co.th/products/angular-i223898871-s342278978.html?spm=a2o6z.10453683.17.3.77ba3002QFHWOq&mp=3