Angular インターセプターでのシームレスな JWT リフレッシュの確保
安全なユーザー セッションを備えた Web アプリでは、有効期間の短い JWT トークンを効果的に管理することが、中断のないユーザー エクスペリエンスにとって重要です。トークンの有効期限が切れると、ユーザーは再ログインを強制されるなどの問題に遭遇することが多く、イライラしてユーザー エンゲージメントが中断される可能性があります。これに対処するために、開発者は通常、期限切れのセッションを処理するために Angular インターセプターを使用して 自動トークン更新 を実装します。 🕰️
このアプローチには、HTTP リクエストをインターセプトし、401 エラー (不正なリクエスト) を捕捉し、更新プロセスを呼び出して新しいトークンを取得することが含まれます。ただし、更新されたトークンまたは Cookie が再試行されたリクエストに確実に適用されるようにする際に問題が発生する可能性があります。新しいトークンが正しく伝播されない場合、再試行が失敗し、ユーザーに同じ認証エラーが発生し、アプリのワークフローが中断される可能性があります。
このガイドでは、このインターセプター パターンの実際的な実装について説明します。エラーをキャッチし、トークンを更新し、有効な承認でリクエストが再試行されることを確認する方法を見ていきます。このアプローチにより、中断が最小限に抑えられ、同時にセッション更新プロセスを制御できるようになります。
最後には、HttpOnly Cookie の処理や、リクエスト量が多いときの更新シーケンスの管理など、一般的な落とし穴に対処する方法についての洞察が得られます。この方法により、アプリケーションは継続的にログインしなくても安全でスムーズなユーザー セッションを維持できます。 🔒
指示 | 使用例 |
---|---|
catchError | Observable パイプライン内で使用され、HTTP リクエスト中に発生するエラーをキャッチして処理します。これにより、インターセプタは、特にトークンの更新や未承認のリクエストの処理のために 401 エラーをインターセプトできるようになります。 |
switchMap | 新しいオブザーバブルに切り替えます。通常、トークンが更新された後の HTTP 再試行を処理するためにここで使用されます。ストリームを切り替えることで、以前のオブザーバブルを置き換え、新しいトークンで再試行されたリクエストのみが処理されるようにします。 |
BehaviorSubject | HTTP リクエスト全体でトークンの更新状態を維持するために使用される特殊な RxJS サブジェクト。通常の Subject とは異なり、BehaviorSubject は最後に出力された値を保持するため、同時発生する 401 エラーの処理に役立ちます。 |
clone | withCredentials: true のような更新されたプロパティを使用して HttpRequest オブジェクトのクローンを作成します。これにより、元のリクエスト構成を保持しながら、リクエストとともに Cookie を送信できるようになります。 |
pipe | 複数の RxJS オペレーターを Observable 内でチェーンします。このインターセプターでは、トークン更新後のエラー処理と再試行ロジックを構成するためにパイプが不可欠です。 |
of | 値からオブザーバブルを作成する RxJS ユーティリティ。テストでは、of(true) を使用して、refreshToken からの成功した応答をシミュレートし、インターセプターの単体テストを支援します。 |
HttpTestingController | Angular のテスト モジュールのユーティリティ。テスト環境で HTTP リクエストの傍受と制御を可能にします。これは、応答をシミュレートし、要求がインターセプターによって正しく処理されたことを確認するのに役立ちます。 |
flush | HttpTestingController とともに使用して、テスト内で HTTP リクエストを手動で完了し、401 Unauthorized などの応答のシミュレーションを可能にします。これにより、インターセプターのリフレッシュ ロジックが期待どおりにアクティブになります。 |
getValue | BehaviorSubject の現在の値にアクセスします。これは、トークンのリフレッシュ プロセスが既に進行中かどうかを確認し、複数のリフレッシュ リクエストを回避するためにこのインターセプターで不可欠です。 |
Angular インターセプターによる信頼性の高い JWT 認証の確保
上記の例では、インターセプターは、401 エラーが発生するたびに有効期間の短い JWT トークンを自動的に更新するように設計されています。この種のセットアップは、セッションのセキュリティを維持することが重要であるが、ユーザー エクスペリエンスが中断されるべきではない、機密データを扱うアプリケーションでは不可欠です。インターセプタは 401 (Unauthorized) エラーを捕捉し、ユーザーに再認証を要求せずにセッションを更新するためのリフレッシュ トークン リクエストを開始します。このプロセスは catchError 関数によってトリガーされ、監視可能なパイプライン内でのエラー処理が可能になります。ここで、HTTP エラー、具体的には 401 は、トークンの有効期限が切れている可能性があることを示し、更新プロセスを開始します。
switchMap 関数は、ここでのもう 1 つの中心要素です。更新されたリクエストに対して新しい監視可能なストリームを作成し、フロー全体をキャンセルすることなく古い監視可能なストリームを置き換えます。更新後、元のリクエストを再試行し、新しいトークンが適用されることを確認します。古いオブザーバブルから新しいオブザーバブルに切り替えることで、インターセプターはシームレスでノンブロッキングな方法でトークンの更新を実行できます。この手法は、安全な認証を維持しながらユーザー対話の中断を減らすため、リアルタイム アプリケーションを操作する場合に特に役立ちます。たとえば、安全な財務ダッシュボードを閲覧しているユーザーが不必要にリダイレクトされたり、ログアウトされたりすることはありません。代わりに、新しいトークンが取得され、バックグラウンドで適用されます。 🔄
さらに、BehaviorSubject は、更新プロセスの状態を管理することによって重要な役割を果たします。この RxJS ユーティリティは最後に出力された値を保持できます。これは、複数のリクエストで同時に 401 エラーが発生した場合に特に役立ちます。複数の更新をトリガーする代わりに、インターセプターは 1 つのトークン更新のみを開始し、他のすべてのリクエストはキューに入れられ、この 1 つのトークン更新を待ちます。 switchMap で BehaviorSubject を使用すると、1 つのリクエストが更新をトリガーした場合、新しいトークンを必要とする他のすべてのリクエストは、更新呼び出しを繰り返すことなく、更新された認証情報を使用するようになります。この機能は、ユーザーが複数のタブを開いている場合、またはアプリが複数の同時ネットワーク呼び出しを管理している場合に非常に役立ち、リソースを節約し、過度のサーバー負荷を回避します。
このインターセプター ロジックをテストすることは、さまざまなシナリオで機能することを確認するためにも不可欠です。そのため、HttpTestingController が含まれています。この Angular テスト ツールを使用すると、制御された環境で 401 Unauthorized ステータスなどの HTTP 応答をシミュレートしてテストできます。 HttpTestingController が提供するメソッドであるフラッシュを使用すると、開発者は実際のエラー応答をシミュレートし、インターセプターが期待どおりに動作することを確認できます。このテスト手法により、アプリをデプロイする前に、更新ロジックがさまざまなケースをどのように適切に処理するかを調整できます。これらのメソッドを使用すると、インターセプターはセッションを安全に保存するだけでなく、アプリを操作するユーザーに、よりシームレスで安定したエクスペリエンスを提供します。 👩💻
Angular を使用した JWT インターセプターの実装: エラー処理とリフレッシュ トークン ソリューション
モジュール式サービス構造で Angular を使用してエラー処理とセッション管理を行う
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { catchError, switchMap } from 'rxjs/operators';
import { Observable, throwError, BehaviorSubject } from 'rxjs';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
private refreshTokenInProgress$ = new BehaviorSubject<boolean>(false);
constructor(private authService: AuthService, private router: Router) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({ withCredentials: true });
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
return this.handle401Error(req, next);
}
return throwError(() => error);
})
);
}
private handle401Error(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!this.refreshTokenInProgress$.getValue()) {
this.refreshTokenInProgress$.next(true);
return this.authService.refreshToken().pipe(
switchMap(() => {
this.refreshTokenInProgress$.next(false);
return next.handle(req.clone({ withCredentials: true }));
}),
catchError((error) => {
this.refreshTokenInProgress$.next(false);
this.authService.logout();
this.router.navigate(['/login'], { queryParams: { returnUrl: req.url } });
return throwError(() => error);
})
);
}
return this.refreshTokenInProgress$.pipe(
switchMap(() => next.handle(req.clone({ withCredentials: true })))
);
}
}
JWT インターセプター トークン リフレッシュ処理のための Angular 単体テスト
Angular のインターセプターでの JWT リフレッシュと HTTP エラー処理のテスト
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { JwtInterceptor } from './jwt.interceptor';
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { AuthService } from './auth.service';
describe('JwtInterceptor', () => {
let httpMock: HttpTestingController;
let authServiceSpy: jasmine.SpyObj<AuthService>;
let httpClient: HttpClient;
beforeEach(() => {
authServiceSpy = jasmine.createSpyObj('AuthService', ['refreshToken', 'logout']);
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
JwtInterceptor,
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
{ provide: AuthService, useValue: authServiceSpy }
]
});
httpMock = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
});
afterEach(() => {
httpMock.verify();
});
it('should refresh token on 401 error and retry request', () => {
authServiceSpy.refreshToken.and.returnValue(of(true));
httpClient.get('/test').subscribe();
const req = httpMock.expectOne('/test');
req.flush(null, { status: 401, statusText: 'Unauthorized' });
expect(authServiceSpy.refreshToken).toHaveBeenCalled();
});
});
Angular インターセプターを使用した JWT トークン リフレッシュ戦略の拡張
Angular を使用する際の重要な側面 JWTトークンインターセプタ 安全なアプリケーションの場合、認証とセッションの有効期限管理の複雑さを効率的に処理できます。単に 401 エラーをキャッチしてトークンを更新するだけでなく、複数のリクエストの処理とトークンの更新を最適化する方法について考えることが重要です。複数のリクエストで同時に 401 エラーが発生した場合、一度に 1 つのトークン更新のみが確実に行われるようにキューまたはロック メカニズムを実装すると、非常に役立ちます。このアプローチにより、不要な API 呼び出しが防止され、特にトラフィックの多いアプリケーションでの負荷が軽減され、キューに入れられたすべてのリクエストが更新後に続行できるようになります。
Angular のインターセプターを使用すると、トークンの保管と取得の処理方法を合理化することもできます。トークンをローカル ストレージにハードコーディングするのではなく、Angular の HTTP のみの Cookie CSRF 保護によりセキュリティが強化されます。 HttpOnly Cookie を使用すると、JavaScript によって JWT にアクセスしたり操作したりすることができなくなり、セキュリティが大幅に向上しますが、更新された Cookie がリクエストで自動的に取得されるようにするという新たな課題が追加されます。 Angular の組み込み withCredentials オプションは解決策であり、各リクエストにこれらの Cookie を含めるようブラウザーに指示します。
実稼働環境では、トークンの更新による負荷の下でアプリケーションがどのように動作するかについてパフォーマンス テストを実行することをお勧めします。テスト設定では、大量のリクエストをシミュレートして、インターセプターのロジックを効率的に拡張できます。実際には、この設定により、ユーザー エクスペリエンスに影響を与えるトークン関連のエラーのリスクが最小限に抑えられます。インターセプター戦略は、適切な Cookie の処理とテストと組み合わせることで、アプリが重要な財務データを管理する場合でも、ソーシャル プラットフォームのユーザー セッションを管理する場合でも、シームレスでユーザー フレンドリーで安全なアプリケーションを維持するのに役立ちます。 🌐🔐
Angular インターセプターを使用した JWT トークン処理に関するよくある質問
- どのようにして catchError JWT トークンの処理をサポートしますか?
- 使用する catchError インターセプター内で 401 エラーを識別し、トークンの有効期限が切れたときにトークンの更新リクエストをシームレスにトリガーできます。
- なぜですか BehaviorSubject の代わりに使用される Subject 更新ステータスを追跡するためですか?
- BehaviorSubject 最後に発行された値を保持するため、複数のリフレッシュ呼び出しをトリガーせずに、同時リクエスト全体のリフレッシュ状態を管理するのに役立ちます。
- どのような役割をするのか switchMap HTTP リクエストを再試行してみてはいかがでしょうか?
- switchMap トークン更新オブザーバブルから再試行された HTTP リクエストへの切り替えを許可し、最新のオブザーバブルのみが完了するようにします。
- Angular でインターセプターをテストするにはどうすればよいですか?
- アンギュラーの HttpTestingController は、401 エラーを含む HTTP 応答をシミュレートして、インターセプター ロジックが正しく動作することを確認するのに役立ちます。
- なぜ使うのか withCredentials クローン化されたリクエスト内で?
- の withCredentials このフラグは、セキュアな HttpOnly Cookie が各リクエストに確実に含まれるようにします。これは、セキュアなセッションを維持するために重要です。
- トラフィックが多い場合にトークンの更新処理を最適化するにはどうすればよいですか?
- 単一の使用 BehaviorSubject ロック メカニズムは、複数の更新リクエストを防止し、高トラフィックのシナリオでのパフォーマンスを向上させるのに役立ちます。
- インターセプターはセッションの有効期限が切れた際のユーザー エクスペリエンスにどのような影響を与えますか?
- インターセプターによりセッションの自動更新が可能になるため、ユーザーが予期せずログアウトすることがなくなり、よりスムーズなユーザー エクスペリエンスが実現します。
- どのようにして clone リクエストの変更に役立ちますか?
- clone 設定などの変更されたプロパティを含むリクエストのコピーを作成します。 withCredentials元のリクエストを変更せずに。
- インターセプターは複数のユーザー セッションで動作しますか?
- はい、ただし、各セッションがその JWT を個別に管理する必要があるか、リフレッシュ ロジックを複数のセッションに適合させる必要があります。
- インターセプターは 401 以外のエラーを処理できますか?
- はい、インターセプターを拡張して、403 Forbidden などの他のエラーをキャッチし、適切に処理して UX を向上させることができます。
Angular アプリケーションでの JWT トークン更新の合理化
効果的な JWT トークン管理は、Angular アプリケーションのユーザー エクスペリエンスとセキュリティの両方を強化するために重要です。 401 エラーをキャッチし、トークンの更新を自動的に開始するインターセプターを実装することで、強制的なログアウトを回避し、シームレスなユーザー フローを提供できます。さらに、リフレッシュ中に同時リクエストを処理するために、 行動主題により、リフレッシュ呼び出しが 1 回だけ行われるようになり、リソースの使用が最適化されます。
最終的な目標は、セキュリティとユーザーの利便性のバランスを取ることです。現実世界のシナリオに合わせてインターセプター ロジックを定期的にテストして改良することで、アプリが大量のリクエストを問題なく処理できるようになります。トークン管理のベスト プラクティスを採用すると、セッション全体で安全でユーザー フレンドリーなエクスペリエンスを維持できます。 👨💻
JWT インターセプターの実装に関する参考資料とリソース
- Angular での HTTP インターセプターの作成に関する詳細については、Angular の公式ドキュメントを参照してください。 Angular HTTP ガイド 。
- JWT トークンの更新メカニズムとベスト プラクティスの管理に関する洞察については、以下を参照してください。 Auth0 のリフレッシュ トークン ガイド 。
- RxJS ライブラリでは、この記事で使用されている演算子に関する広範な詳細が提供されています。 スイッチマップ そして キャッチエラー: RxJS オペレーターガイド 。
- Angular テスト戦略の場合 HttpTestingController、Angular のテスト ユーティリティのリソースを確認してください。 Angular HTTP テスト ガイド 。