JavaScript フレームワークでのトークン認証とリクエスト再試行:Angular 4 Interceptor を用いた実装例
Angular 4 Interceptor でトークン更新後のリクエスト再試行
RxJS と Angular HTTP Interceptor を使用して、以下の手順で実装できます。
HTTP Interceptor を作成する
まず、HTTP_INTERCEPTORS
プロバイダーに登録する HTTP Interceptor を作成する必要があります。
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// トークンを取得
const token = this.authService.getAuthToken();
// リクエストヘッダーにトークンを追加
if (token) {
req = req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) });
}
// リクエストを処理
return next.handle(req)
.pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// 成功した場合は何もしない
}
}),
catchError((error: HttpErrorResponse) => {
// 401 エラーの場合、トークンを更新して再試行
if (error.status === 401) {
return this.authService.refreshToken()
.pipe(
switchMap(() => {
// トークン更新後に同じリクエストを再試行
return next.handle(req);
}),
catchError((innerError: HttpErrorResponse) => {
// トークン更新失敗の場合はエラーを返す
return of(innerError);
})
);
}
// その他のエラーはそのまま返す
return of(error);
})
);
}
}
Interceptor を登録する
次に、app.module.ts
ファイルで作成した Interceptor を登録する必要があります。
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 './auth.interceptor';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
AuthService を作成する
AuthService
は、認証トークンを管理するサービスです。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@Injectable()
export class AuthService {
private token: string;
constructor(private http: HttpClient) { }
getAuthToken(): string {
return this.token;
}
refreshToken(): Observable<string> {
// リフレッシュトークンを使用して新しいアクセス トークンを取得
return this.http.post('/api/refresh-token', { refreshToken: this.refreshToken })
.pipe(
map((response: any) => response.accessToken),
tap(token => this.token = token)
);
}
}
使い方
上記の手順で作成した Interceptor と AuthService を使用して、認証トークンが期限切れになった場合にリクエストを自動的に再試行することができます。
例えば、以下のコードのように、HttpClient
を使用して API リクエストを行うことができます。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class MyService {
constructor(private http: HttpClient) { }
getData(): Observable<any> {
return this.http.get('/api/data');
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@Injectable()
export class AuthService {
private token: string;
constructor(private http: HttpClient) { }
getAuthToken(): string {
return this.token;
}
refreshToken(): Observable<string> {
// リフレッシュ トークンを使用して新しいアクセス トークンを取得
return this.http.post('/api/refresh-token', { refreshToken: this.refreshToken })
.pipe(
map((response: any) => response.accessToken),
tap(token => this.token = token)
);
}
}
auth.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// トークンを取得
const token = this.authService.getAuthToken();
// リクエストヘッダーにトークンを追加
if (token) {
req = req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) });
}
// リクエストを処理
return next.handle(req)
.pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// 成功した場合は何もしない
}
}),
catchError((error: HttpErrorResponse) => {
// 401 エラーの場合、トークンを更新して再試行
if (error.status === 401) {
return this.authService.refreshToken()
.pipe(
switchMap(() => {
// トークン更新後に同じリクエストを再試行
return next.handle(req);
}),
catchError((innerError: HttpErrorResponse) => {
// トークン更新失敗の場合はエラーを返す
return of(innerError);
})
);
}
// その他のエラーはそのまま返す
return of(error);
})
);
}
}
app.module.ts
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 './auth.interceptor';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class MyService {
constructor(private http: HttpClient) { }
getData(): Observable<any> {
return this.http.get('/api/data');
}
}
このコードの説明
- AuthInterceptor
- AuthService
getAuthToken()
: 現在保存されている認証トークンを返します。refreshToken()
: リフレッシュ トークンを使用して新しいアクセス トークンを取得し、token
プロパティに保存します。
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { tap, catchError, retryWhen } from 'rxjs/operators';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// トークンを取得
const token = this.authService.getAuthToken();
// リクエストヘッダーにトークンを追加
if (token) {
req = req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) });
}
// リクエストを処理
return next.handle(req)
.pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// 成功した場合は何もしない
}
}),
catchError((error: HttpErrorResponse) => {
// 401 エラーの場合、トークン更新を試みる
if (error.status === 401) {
return this.authService.refreshToken()
.pipe(
switchMap(() => {
// トークン更新後に同じリクエストを再試行
return next.handle(req);
}),
catchError(() => {
// トークン更新失敗の場合はエラーを返す
return throwError(error);
})
);
}
// その他のエラーはそのまま返す
return throwError(error);
}),
retryWhen(errors => {
return errors.pipe(
mergeMap(error => {
if (error.status === 401) {
// トークン更新を試みる
return this.authService.refreshToken().pipe(
tap(() => {
// トークン更新成功
console.log('トークンを更新しました。');
}),
catchError(() => {
// トークン更新失敗
console.error('トークン更新に失敗しました。');
// 再試行を停止
return of(throwError(error));
})
);
}
// 401 エラー以外の場合、再試行を停止
return of(throwError(error));
})
);
})
);
}
}
ErrorHandler を使用する
ErrorHandler
を使用して、401 エラーを検出してトークンを更新し、リクエストを再試行することもできます。
import { Injectable } from '@angular/core';
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private router: Router, private authService: AuthService) { }
handleError(error: Error | HttpErrorResponse): Observable<never> {
if (error instanceof HttpErrorResponse && error.status === 401) {
// トークン更新を試みる
return this.authService.refreshToken()
.pipe(
switchMap(() => {
// トークン更新後にエラーが発生したページにリダイレクト
this.router.navigateByUrl(error.url);
return of(null);
}),
catchError(() => {
// トークン更新失敗
console.error('トークン更新に失敗しました。');
// エラー画面にリダイレクト
this.router.navigateByUrl('/error');
return of(null);
})
);
}
// その他のエラーはデフォルトのエラーハンドラーに処理させる
return of(null);
}
}
RxJS Observable を使用する
RxJS Observable を使用して、トークン更新とリクエスト再試行を非同期的に処理することもできます。
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@
javascript angular rxjs