/
request.interceptor.ts
128 lines (111 loc) · 6.45 KB
/
request.interceptor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { Injectable } from '@angular/core';
import { HttpClient, HttpInterceptor, HttpRequest, HttpHandler, HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpErrorResponse } from "@angular/common/http";
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/take';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { RefreshTokenService } from '../services/refresh-token.service'
import { AuthService } from '../services/auth.service';
import { LoginService } from '../services/login.service';
@Injectable()
// HttpInterceptor interface.
export class RequestInterceptor implements HttpInterceptor {
isRefreshingToken: boolean = false;
tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
constructor(private authService: AuthService, public login: LoginService, private refreshToken: RefreshTokenService) {}
// addToken will add the Bearer token to the Authorization header
addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
return req.clone({ setHeaders: { Authorization: 'Bearer ' + token }})
}
// The RequestInterceptorService will implement HttpInterceptor which has only one method:
// intercept. It will add a token to the header on each call and catch any errors that might occur.
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
// In the intercept method, we return next.handle and pass in the cloned request with a header added
// Get the auth token from the AuthService
console.log('here we go');
return next.handle(this.addToken(req, this.authService.getToken()))
.catch(error => {
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 401:
console.log('error 401');
return this.handle401Error(error);
case 403:
console.log('error 403');
return this.handle403Error(req, next);
}
} else {
return Observable.throw(error);
}
});
}
// The code to handle the 401 error is the most important.
handle403Error(req: HttpRequest<any>, next: HttpHandler) {
// If isRefreshingToken is false (which it is by default) we will
// enter the code section that calls authService.refreshToken
if (!this.isRefreshingToken) {
// Immediately set isRefreshingToken to true so no more calls
// come in and call refreshToken again – which we don’t want of course
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.tokenSubject.next(null);
// Call authService.refreshToken (this is an Observable that will be returned)
return this.refreshToken.refreshToken()
.switchMap((newToken: string) => {
if (newToken) {
// When successful, call tokenSubject.next on the new token,
// this will notify the API calls that came in after the refreshToken
// call that the new token is available and that they can now use it
this.tokenSubject.next(newToken);
// Return next.handle using the new token
return next.handle(this.addToken(req, newToken));
}
// If we don't get a new token, we are in trouble so logout.
return this.login.logout();
})
.catch(error => {
// If there is an exception calling 'refreshToken', bad news so logout.
return this.login.logout();
})
.finally(() => {
// When the call to refreshToken completes, in the finally block,
// reset the isRefreshingToken to false for the next time the token needs to be refreshed
this.isRefreshingToken = false;
});
// Note that no matter which path is taken, we must return an Observable that ends up
// resolving to a next.handle call so that the original call is matched with the altered call
}
// If isRefreshingToken is true, we will wait until tokenSubject has a non-null value
// – which means the new token is ready
else {
return this.tokenSubject
.filter(token => token != null)
// Only take 1 here to avoid returning two – which will cancel the request
.take(1)
.switchMap(token => {
// When the token is available, return the next.handle of the new request
return next.handle(this.addToken(req, token));
});
}
}
handle401Error(error) {
console.log('outside conditional');
// Some may be wondering at this point what would happen if the refresh token times out.
// Usually caused by not making any API calls for whatever the timeout is configured for.
// Well, what happens is that you should see a 400 error with an ‘invalid_grant’ message.
// So the handle401Error code should most likely log out the user and direct them to the login page.
if (error && error.status === 401 || error.error && error.error.error === 'invalid_grant') {
// If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout.
console.log('inside the conditional');
return this.login.logout();
}
return Observable.throw(error);
}
}