ในบทความนี้ เราจะสร้าง AuthInterceptor ซึ่งจะดักจับทุกๆ request เพื่อเช็ค token ก่อนส่งไปยังเซิร์ฟเวอร์ หากพบว่า token หมดอายุ ระบบจะเรียกใช้ refresh token และอัปเดต access token ใหม่โดยอัตโนมัติ
ทำไมต้องใช้ AuthInterceptor?
การมี AuthInterceptor ช่วยให้คุณสามารถ
- จัดการการหมดอายุของ Token: อัตโนมัติให้ผู้ใช้เข้าสู่ระบบใหม่โดยไม่ต้องทำให้การใช้งานหยุดชะงัก
- เพิ่มความปลอดภัย: ปกป้องข้อมูลผู้ใช้โดยการใช้ Bearer token ในทุกๆ request
- ประสบการณ์ผู้ใช้ที่ดีขึ้น: ผู้ใช้ไม่ต้องล็อกอินซ้ำเมื่อ token หมดอายุ
โครงสร้างโค้ด AuthInterceptor
- ตั้งค่า AuthInterceptor ในโมดูลหลัก
ใน Angular, เพื่อให้ AuthInterceptor ทำงานได้ คุณต้องทำการเพิ่มมันลงใน providers ของ AppModule ดังนี้
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AuthInterceptor } from '../shared/interceptor/auth.interceptor';
@NgModule({
declarations: [
AppComponent,
// ส่วนประกาศคอมโพเนนต์ต่าง ๆ
],
imports: [
BrowserModule,
HttpClientModule, // ต้องเพิ่ม HttpClientModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
],
bootstrap: [AppComponent]
})
export class AppModule { }
- โค้ดของ AuthInterceptor
โค้ดด้านล่างนี้เป็นตัวอย่างของ AuthInterceptor ที่ดักจับ request และทำการเช็คว่า access token หมดอายุหรือไม่ หากหมดอายุจะทำการเรียก refresh token อัตโนมัติ
import { Injectable } from '@angular/core';
import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { CookieService } from 'ngx-cookie-service';
import { AuthService } from '../service/authen/auth.service';
import {
BehaviorSubject,
catchError,
filter,
finalize,
first,
Observable,
switchMap,
throwError,
} from 'rxjs';
interface IAuthRes {
accessToken: string;
refreshToken: string;
}
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<string> =
new BehaviorSubject<string>('');
constructor(
private cookieService: CookieService,
private authService: AuthService
) { }
intercept(request: HttpRequest<any>, next: HttpHandler): any {
let modifiedRequest = this.setAuthHeader(
request,
this.cookieService.get('accessToken'),
);
if (request.url.includes('/api/auth/refreshToken')) {
modifiedRequest = this.setAuthHeader(
request,
this.cookieService.get('refreshToken'),
);
}
return next.handle(modifiedRequest).pipe(
catchError((error: HttpErrorResponse) => {
if (
error.status == 401 &&
!request.url.includes('/api/auth/refreshToken')
) {
return this.refreshAuth(request, next);
} else if (error.status === 403) {
console.log("ไม่มีสิทธิ์เข้าใช้งาน");
}
return throwError(() => error);
}),
);
}
refreshAuth(
request: HttpRequest<any>,
next: HttpHandler,
): Observable<HttpEvent<any>> {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next('');
return this.authService
.getRefreshToken(this.cookieService.get('refreshToken'))
.pipe(
switchMap((res: IAuthRes) => {
this.cookieService.set('accessToken', res.accessToken, {
path: '/',
secure: true,
sameSite: 'Strict',
});
this.cookieService.set('refreshToken', res.refreshToken, {
path: '/',
secure: true,
sameSite: 'Strict',
});
this.refreshTokenSubject.next(res.accessToken);
return next.handle(this.setAuthHeader(request, res.accessToken));
}),
catchError((error) => {
console.log('TOKEN หมดอายุ กรุณา Login เพื่อเข้าสู่ระบบใหม่');
return throwError(() => error);
}),
finalize(() => (this.isRefreshing = false)),
);
}
return this.refreshTokenSubject.pipe(
filter((token) => Boolean(token)),
first(),
switchMap((token) => next.handle(this.setAuthHeader(request, token))),
);
}
setAuthHeader(request: HttpRequest<any>, accessToken: string) {
return request.clone({
setHeaders: {
'Authorization': `Bearer ${accessToken}`,
},
});
}
}
- AuthService สำหรับการจัดการ refresh token
AuthService จะเรียก API เพื่อขอ access token ใหม่เมื่อ token หมดอายุ โดยส่ง refresh token ไปให้เซิร์ฟเวอร์ตรวจสอบ
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { RefreshToken } from './model/auth.model';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private httpClient: HttpClient) {}
getRefreshToken(token: string): Observable<RefreshToken> {
const url = `http://localhost:4001/api/refreshToken`;
return this.httpClient.post<RefreshToken>(url, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
}
}
สรุป
การใช้ AuthInterceptor ช่วยให้คุณสามารถจัดการการหมดอายุของ token เมื่อ access token หมดอายุ ระบบจะทำการเรียกใช้ refresh token อัตโนมัติ ซึ่งทำให้ผู้ใช้สามารถเข้าถึงข้อมูลหรือดำเนินการต่างๆ ได้โดยไม่ต้องล็อกอินซ้ำ ช่วยให้ระบบมีความปลอดภัย และมีประสบการณ์การใช้งานที่ราบรื่น