I am setting custom headers in nodejs (express) application within the response:
res.header('Vme','true')
next()
When i get response back to client I can see headers correctly in the browser:
browserHeaders
The problem is that i am not able to access headers inside angular hhtp interceptors:
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.loaderService.show()
return next.handle(request)
.pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
const message = event?.body?.message
if (message?.length) {
this.toastService.show(event?.body?.message, SeveritiesEmun.Success)
}
}
return event
}),
catchError(error => {
let errorMessage = error?.error || 'Error'
if (typeof errorMessage === 'object') {
errorMessage = error?.statusText || 'Error'
}
this.toastService.show(errorMessage, SeveritiesEmun.Fail)
return throwError(errorMessage)
}),
finalize(() => {
this.loaderService.hide()
})
)
}
In both the error (error) and the successful instance ((event: HttpEvent ))
the "headers" property contains no value.
You have to process the send data like this:
import { tap } from "rxjs/operators";
next.handle(req).pipe(
tap((event: HttpEvent<any>): void => {
if (event instanceof HttpResponse) {
// do whatever with event
}
})
);
Then you can check if your header is applied by doing event.headers.has("Vme") and access it by event.headers.get("Vme")
Related
I need help creating a function that gets the categories from my API and also checks for status of the call. I have written my function like the code bellow but it keep showing me the bellow error:
Argument of type '(token: GetResult) => Subscription' is not assignable to parameter of type '(value: GetResult, index: number) => ObservableInput<any>'
Here is the code of my function:
getCategories() {
return from(Preferences.get({ key: "TOKEN_KEY" })).pipe(
switchMap((token) => {
const headers = new HttpHeaders().set(
"Authorization",
`Bearer ${token.value}`
);
return this.httpClient
.get(`${environment.apiUrl}categories`, {
headers,
observe: "response",
})
.subscribe(
(res) => {
return res.body;
},
(error) => {
console.log(error.status);
if (error.status === 400) {
console.log(error.error.message);
}
if (error.status === 401) {
this.authService.logout();
this.router.navigateByUrl("/login", { replaceUrl: true });
}
}
);
})
);
}
You should not use .subscribe inside a subscription. .subscribe returns a Subscription which can't be assigned to the Observable that a switchMap should return.
Use catchError to to handle the error case and a map to handle the success case.
getCategories() {
return from(Preferences.get({ key: 'TOKEN_KEY' })).pipe(
switchMap(token => {
const headers = new HttpHeaders().set('Authorization', `Bearer ${token.value}`);
return this.httpClient.get(
`${environment.apiUrl}categories`,
{ headers, observe: 'response' }
)
}),
catchError(error => {
console.log(error.status);
if (error.status === 400) {
console.log(error.error.message);
}
if (error.status === 401) {
this.authService.logout();
this.router.navigateByUrl('/login', { replaceUrl: true });
}
return EMPTY
}),
map(res => res.body)
)
};
I am trying to call the MSAL silentTokenrefresh method from Angular authInterceptor whenever the 401 hits. And then i am trying to recall the failed request again with a new token so the service won't be interrupted. I have followed this stackoverflow link (answered by Andrei Ostrovski) and implemented the same in my application.
There are two problem with refreshToken() method.
the catchError expects return variable where i put EMPTY. The code is happy, but it is NOT triggering back the failed request where the total purpose is not achieved. It means that whenever it encounters 401 and it is allowing to acquire the new token and then it is not triggering the failed requests.
To resolve the above one, i just took another approach (instead of pipe, i used subscribed but there the return is not applicable) and then it became completely invalid.
Could you please suggest me here to make it work?
My complete authInterceptor file:
addAuthHeader(request) {
const tokenType = "Bearer";
const authHeader = this.sessionService.getAccessToken();
if (authHeader) {
return request.clone({
setHeaders: {
"Authorization": tokenType + " "+ authHeader
}
});
}
return request;
}
refreshToken(): Observable<any> {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
// Getting the scope.
const loginRequest: { scopes: any } = {
scopes: ['openId','profile'],
};
return this.msalAuthService.acquireTokenSilent(loginRequest).pipe(
tap((payloadInfo)=>{
this.authServices.setSessions(payloadInfo);
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
}),
catchError(() => {
this.refreshTokenInProgress = false;
this.authServices.logout();
return EMPTY;
})
)
}
}
handleResponseError(error, request?, next?) {
// Invalid token error
if (error.status === 401) {
return this.refreshToken().pipe(
switchMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
}),
catchError(e => {
if (e.status !== 401) {
return this.handleResponseError(e);
} else {
this.authentiCationService.logout();
this.router.navigate(["logout"], {
replaceUrl: true
});
}
}));
}
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.totalRequests++;
req = req.clone({ headers: req.headers.set("Accept", "*/*") });
req = req.clone({
headers: req.headers.set("Content-Type", "application/json")
});
req = this.addAuthHeader(req);
const started = Date.now();
return next.handle(req).pipe(
tap(
(event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// some internal logic for the success scenario
}
},
(err: any) => {
this.handleResponseError(err, req, next);
}
)
);
}
The compile time error you are getting is because you are returning the Subscription instead of the Observable. Please take a look at the difference between them: https://angular.io/guide/observables
You have to return the Observable from your code. In catchError return Observable.throw(error.statusText);
The thing is if I have a 401 error (token expired) I am making a request for the server to refresh the token. It handles all the requests been sent. It refreshes the token but interrupts the previous request. How to continue request after cathing error?
private handleAuthError(err: HttpErrorResponse): Observable<any> {
if (err.status === 401 || err.status === 403) {
this.authService.refreshToken(this.userData.accessToken, this.userData.refreshToken, this.userData.tokenType).subscribe((resp: any) => {
this.userData.accessToken = resp.access_token;
this.authService.currentUserSubject.next(this.userData);
const userData: User = new User(resp);
localStorage.setItem('currentUser', JSON.stringify(this.authService.currentUserSubject));
localStorage.setItem(`${JWT_TOKEN_KEY}`, JSON.stringify(userData.accessToken));
})
return of(err.message);
}
return throwError(err);
}
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token: string = JSON.parse(localStorage.getItem(`${JWT_TOKEN_KEY}`));
const isLoggedIn = token;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
console.log(request);
return next.handle(request)
.pipe(
catchError((x: any) => this.handleAuthError(x)));
}
}
Just make a new request, you can't use the same one since it already ended
You can try this function to catch any request while error occurs and rewind that request again.
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
return event;
}
}),
catchError(error => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
if (request.url !== Constants.AUTH_ENDPOINT) {
this.auth.collectFailedRequest(request);
return this.auth.refreshAuthToken().pipe(
switchMap((data: any) => {
if (data && data.access_token && data.refresh_token) {
localStorage.setItem(Constants.ACCESS_TOKEN, JSON.stringify(data.access_token));
localStorage.setItem(Constants.REFRESH_TOKEN, JSON.stringify(data.refresh_token));
this.auth.cachedRequests = this.auth.cachedRequests.clone({
setHeaders: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${data.access_token}`
}
});
return next.handle(this.auth.cachedRequests); // rewinds previously requested http call
} else {
toastr.error(error.error.toUpperCase(), error.status);
this.auth.removeAllTokens();
setTimeout(() => {
window.location.reload();
}, 1000);
}
})
);
} else {
if (request.body.grant_type === 'refresh') {
this.auth.removeAllTokens();
window.location.reload();
} else {
console.log('no refresh grant type');
}
}
} else {
return throwError(error);
}
}
})
);
}
I am using Angular 7 and I have used HTTP Interceptor in my application.
I am not able to track 401 status code in that HTTP Interceptor.
I tried pitting catchError and tap but it gives status 0, I want 401.
My code is here:
return next.handle(request).pipe(tap(event => {
console.log('the ev is :', event);
if (event instanceof HttpResponse) {
this.onEnd();
}
}, err => {
if (err instanceof HttpErrorResponse) {
console.log('the eeeeeee is :', err);
this.onEnd();
// User logged out
if (err.status === 401) {
this.router.navigate(['logout']);
}
}
})).pipe(catchError(error => {
console.log('the ee is :', error);
return Observable.throw(error);
}));
Thanks.
You have an error handler in the tap method and then a separately piped catchError. Unless you intend to act on the received response i.e http 200. A simple interceptor implementation would be:
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(err => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401 || err.status === 403) {
// Invalidate user session and redirect to login/home
}
// return the error back to the caller
return throwError(err);
}
}),
finalize(() => {
// any cleanup or final activities
})
);
}
I'm using angular 6 and this redirects to the login page when there's an error 401 or 403. I think it should work in angular 7. There should be other ways to do it but I share what works for me in this case. Hope it helps.
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError(
err =>
new Observable<HttpEvent<any>>(observer => {
if (err instanceof HttpErrorResponse) {
const errResp = <HttpErrorResponse>err;
if (errResp.status === UNAUTHORIZED || err.status === FORBIDDEN) {
this.authService.goToLogin(this.router.url);
}
}
observer.error(err);
observer.complete();
})
)
);
}
Maybe someone can suggest me what's the better way to carry out this kind of stuff.
import { Injectable } from '#angular/core'
import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse } from '#angular/common/http'
import { Router } from '#angular/router'
import { Observable, of } from 'rxjs'
import { catchError } from 'rxjs/operators'
#Injectable()
export class ServerErrorInterceptor implements HttpInterceptor {
constructor(public router: Router) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: any) => {
if (error.status == 401 || error.status == 0) {
this.router.navigate(['/'])
} else {
}
return of(error)
}),
)
}
}
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ServerErrorInterceptor,
multi: true
}
]
Try this
#Injectable()
export class HTTPListener implements HttpInterceptor {
constructor(private status: HTTPStatus) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
map(event => {
return event
}),
catchError(err => {
if (err.status === 401) {
}
const error = err.error.message || err.statusText
return throwError(error)
}),
finalize(() => {}),
)
}
}
Here is AuthInterceptor:
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const Token = this.authService.getToken();
if (!Token) {
return next.handle(req);
}
// Refresh Token first
if (Token.expiresRefreshToken && Number(Token.expiresRefreshToken) < Date.now()) {
this.authService.refreshTokenRefresh(Token.tokenref)
.subscribe((response) => {
localStorage.setItem('tokenref', response.tokenref);
localStorage.setItem('tokenrefexp', response.tokenrefexp);
});
}
// Then next Access Token
if (Token.expiresToken && Number(Token.expiresToken) < Date.now()) {
this.authService.refreshToken(Token.tokenref)
.subscribe((response) => {
localStorage.setItem('token', response.token);
localStorage.setItem('tokenexp', response.tokenexp);
});
}
// Original request with updated custom headers
return next.handle(req.clone({
headers: req.headers
.set('Authorization', 'Bearer ' + localStorage.getItem('token'))
.set('X-Auth-Provider', localStorage.getItem('provider'))
}));
}
}
I need to evaluate those conditions before sending the request because some custom headers may change after methods refreshToken and refreshTokenRefresh. Is there a way to evaluate everything inside a RxJS operator? First condition (refreshTokenRefresh), then second (refreshToken) and finally the req.
Update: I'm getting this error: RangeError: Maximum call stack size exceeded. How to fix this?
We want to wait until some requests will be completed (evaluate order does not matter?) than do another request.
const queue = this.handleRefreshToke(this.handleRefreshTokenRefresh([])); - place there all request that should be done before we call next.handle.
Use the forkJoin to wait until all request (placed in queue) will be completed than map to another Obervable ( mergeMap ).
PS We could also move handleRefreshTokenRefresh and handleRefreshToke to separated HttpInterceptor.
EDITED To prevent recursive call of interceptors we should skip interceptors for refreshTokens call.
export const InterceptorSkipHeader = 'X-Skip-Interceptor';
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
handleRefreshTokenRefresh(queue: Observable<void>[]) {
const Token = this.authService.getToken();
if (Token.expiresRefreshToken &&
const req = this.authService.refreshTokenRefresh(Token.tokenref)
.pipe(tap((response) => {
localStorage.setItem('tokenref', response.tokenref);
localStorage.setItem('tokenrefexp', response.tokenrefexp);
}));
return [...queue, req];
}
return queue;
}
handleRefreshToke(queue: Observable<void>[]) {
const Token = this.authService.getToken();
if (Token.expiresToken && Number(Token.expiresToken) < Date.now()) {
const req = this.authService.refreshToken(Token.tokenref)
.subscribe((response) => {
localStorage.setItem('token', response.token);
localStorage.setItem('tokenexp', response.tokenexp);
});
return [...queue, req];
}
return queue;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.headers.has(InterceptorSkipHeader)) {
const headers = req.headers.delete(InterceptorSkipHeader);
return next.handle(req.clone({ headers }));
}
const Token = this.authService.getToken();
if (!Token) {
return next.handle(req);
}
const queue = this.handleRefreshToke(this.handleRefreshTokenRefresh([]));
return forkJoin(queue).pipe(
mergeMap(()=>{
return next.handle(req.clone({
headers: req.headers
.set('Authorization', 'Bearer ' + localStorage.getItem('token'))
.set('X-Auth-Provider', localStorage.getItem('provider')),
}));
})
);
}
}
Add InterceptorSkipHeader to refreshTokens to skip interceptors.
// AuthService
refreshTokenRefresh(token){
const headers = new HttpHeaders().set(InterceptorSkipHeader, '');
return this.httpClient
.get(someUrl, { headers })
}
refreshToken(token){
const headers = new HttpHeaders().set(InterceptorSkipHeader, '');
return this.httpClient
.get(someUrl, { headers })
}