Angular catch errors in a central place or component - authentication

I have an Angular 9 project that works with a .NET Core WEB API.
The site features user authentication that is role based. The API returns two types of errors: 401 and 403 depending on the authorization rules for a controller method.
My goal is to display a message in my layout component when the API returns any of these errors, for example:
401: 'Session has expired, please log in.'
403: 'You do not have access to this feature.'
Is there any way to catch these errors in Angular and know which one was thrown?

You can use interceptors to catch every http requests. In your case you need to catch response. To do it;
At the module which you provided HttpClientModule
(app/core).module.ts
import { HTTP_INTERCEPTORS, HttpClientModule } from '#angular/common/http';
import { HttpErrorInterceptor } from '<path of interceptor>';
#NgModule({
imports: [HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorInterceptor,
multi: true,
},
]
});
error.interceptor.ts
export class HttpErrorInterceptor implements HttpInterceptor {
constructor(
// your injections if you need
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err: HttpErrorResponse) => {
switch (err.status) {
case 401: {
this.handle401();
}
case 403: {
this.handle403();
}
}
return throwError(err);
}),
);
}

Related

i18n-backend with oauth2 authorization in spartacus

We want to use a backend for i18n in spartacus. Unfortunately this backend needs an oauth2 authentication but spartacus does not send a bearer token when trying to access this webservice endpoint and we get a 401 error. Is there anything we can do?
Right now we try to solve this problem in this way:
What we need to have is implemented in ClientTokenInterceptor, so we adapted this interceptor, changed the if-clause a little bit so it fits to the backend-url for this webservices and provide the interceptor via app.module.ts which works so far. Unfortunately calling this.authService.getClientToken() in our Interceptor returns no token.
constructor(
private authService: AuthService,
private occEndpoints: OccEndpointsService
) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return this.getClientToken(request).pipe(
take(1),
switchMap((token: ClientToken) => {
if (
token &&
request.url.includes("i18n")
) {
request = request.clone({
setHeaders: {
Authorization: `${token.token_type} ${token.access_token}`,
},
});
}
return next.handle(request);
})
);
}
private getClientToken(request: HttpRequest<any>): Observable<ClientToken> {
if (
InterceptorUtil.getInterceptorParam(USE_CLIENT_TOKEN, request.headers)
) {
return this.authService.getClientToken();
}
return of(null);
}
What do we miss?
Actually there are couple of things not needed in your solution.
I pasted below what I did instead and tested that it is working correctly (and you can see authorization data in the translation files requests).
First issue:
InterceptorUtil.getInterceptorParam(USE_CLIENT_TOKEN, request.headers) you don't need to check that. If you always need the auth data for translation requests just use return this.authService.getClientToken();
Second issue:
In intercept method you didn't cover cases for any other request than translation. Because of that the request for the client token would hang here, because it would wait for token and so on. If you add option for any other case than i18n it starts working as you intend.
Working solution:
#Injectable({ providedIn: 'root' })
export class TranslationsInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (request?.url?.includes('i18n')) {
return this.getClientToken().pipe(
take(1),
switchMap((token: ClientToken) => {
if (token) {
request = request.clone({
setHeaders: {
Authorization: `${token.token_type} ${token.access_token}`,
},
});
}
return next.handle(request);
})
);
} else {
return next.handle(request);
}
}
private getClientToken(): Observable<ClientToken> {
return this.authService.getClientToken();
}
}

Customise the response on verification failure for a jwt Strategy NestJs

I successfully implemented a jwt strategy for authentication using nestJs.
Below is the code for the jwt strategy
import { ServerResponse } from './../helpers/serverResponse.helper';
import { Injectable, UnauthorizedException, HttpStatus } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { config as env } from 'dotenv';
import { Bugsnag } from '../helpers/bugsnag.helper';
env();
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
private readonly logger: Bugsnag,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET_KEY,
passReqToCallback: true,
});
}
async validate(payload, done: Function) {
try {
const validClaims = await this.authService.verifyTokenClaims(payload);
if (!validClaims)
return done(new UnauthorizedException('invalid token claims'), false);
done(null, payload);
} catch (err) {
this.logger.notify(err);
return ServerResponse.throwError({
success: false,
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: 'JwtStrategy class, validate function',
errors: [err],
});
}
}
}
I saw here that the validate function will be called only when a valid token was provided in the request headers and I'm okay with that. However, I would like to know if it is possible to customize the response object which is sent in that case (invalid token provided).
If yes, how do I do that ?
You can use a exception filter to catch UnauthorizedExceptions and modify the response there if you'd like. The other option would be extending the AuthGuard('jwt') mixin class and adding in some logic around a try/catch for the super.canActivate(context), then in the error read what the reason is and throw a specific UnauthorizedException with your custom message
You can use the AuthGuard('jwt')'s handleRequest method to throw any exception on JWT Validation failure.
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err: any, user: any, info: any, context: any, status: any) {
if (info instanceof JsonWebTokenError) {
throw new UnauthorizedException('Invalid Token!');
}
return super.handleRequest(err, user, info, context, status);
}
}
JsonWebTokenError comes from jsonwebtoken library, which is used internally by passport.

Http interceptor on response not working in angular 5

I want the user to be automatically logged out if any api returns a 401 error response.And to do that I am intercepting every request and as soon as the error code comes 401 I am clearing the jwt token in my local storage and auth guard prevents the user from jumping to that route.But after implementing the interceptor(examples are very less for this and no mention in the docs as well) I am unable to hit any HTTP request.Below is my code.Thanks in advance.
import { Injectable, Injector } from '#angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse} from '#angular/common/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/observable/throw'
import 'rxjs/add/operator/catch';
#Injectable()
export class ResponseInterceptor implements HttpInterceptor {
constructor() {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).do((event: HttpEvent<any>) => {
}, (err: any) => {
if (err instanceof HttpErrorResponse) {
// do error handling here
console.log('and the error is ');
console.log(err)
}
});
}
}
If it goes with error why you need to track every request if you could only catch needed?
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err:string, caught:Observable<any>)=>this.handleHttpClientError(err, caught))
);
}
handleHttpClientError(error: any, caught: Observable<any>)
{
if(error.status == 401){
... your logic here ...
}
return new EmptyObservable<Response>();
// or return Observable.throw('Auth error');
}

Interceptor code not getting called when using http.post

I have written (copied from SO!) the following interceptor code. I want to modify the outgoing request and also intercept the response. However, I have noticed that the response never gets intercepted. Why? Is it because I am using Rxjs?
Interceptor code
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from "#angular/common/http";
import {Injectable} from "#angular/core";
import {Observable} from "rxjs/Observable";
import 'rxjs/add/operator/do';
#Injectable()
export class CustomInterceptor implements HttpInterceptor {
constructor() {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("outgoing request",request);
request = request.clone({
withCredentials: true
});
console.log("new outgoing request",request);
return next
.handle(request)
.do((ev: HttpEvent<any>) => {
if (ev instanceof HttpResponse) {
console.log('processing response', ev); //I DON'T SEE THIS PRINT
}
});
}
}
I am sending the request to the server as follows
return this.http.post(/*this.API_URL+*/this.SIGNIN_USER_URL,body,httpOptions)
.map(response=>{
console.log('response from backend service',response); //I SEE THIS PRINT
let result= <ServerResponse>response;
console.log("result is "+result.result+' with additional information '+result.additionalInformation)
return result;
})
.catch(this.handleError);
Why is the interceptor code not getting hit?
Update - the provider code snippet in app.module.ts
providers: [WebToBackendInterfaceService, //some other provider
{
provide: HTTP_INTERCEPTORS,
useClass: CustomInterceptor , //interceptor provider
multi: true
}],
I added a print between do and if (console.log("got an event",ev). I can see the following message on console `got an event
{…}
​body: Object { result: "success", "additional-info": "found user" }
​headers: {…}
​lazyInit: function lazyInit()
​​lazyUpdate: null
​​normalizedNames: Map
​​​size: 0
​​​<entries>
​​​__proto__: Object { … }
​​__proto__: Object { has: has(), get: get(), keys: keys(), … }
​ok: true
​status: 200
​statusText: "OK"
​type: 4
​url: "http://localhost:9000/ws/users/signin"
​__proto__: Object { constructor: HttpResponse(), clone: clone() }`
I dont know why the if statement isn't getting executed as it seems the event is of type HttpResponse (referring to ​__proto__: Object { constructor: HttpResponse(), clone: clone() })

ORIGINAL EXCEPTION: No provider for Auth! : Ionic Cloud Services

I set up Ionic Cloud Service and went through the initial process of authorizing a user.
import {Component} from '#angular/core';
import {NavController} from 'ionic-angular';
import {Auth, User, UserDetails, IDetailedError} from '#ionic/cloud-angular';
#Component({
templateUrl: 'build/pages/signup/signup.html'
})
export class SignupPage {
constructor(public auth: Auth, public user: User){
let details: UserDetails = {'email': 'hi#ionic.io', 'password': 'puppies123'};
this.auth.signup(details).then(() => {
// `this.user` is now registered
}, (err: IDetailedError<string[]>) => {
for (let e of err.details) {
if (e === 'conflict_email') {
alert('Email already exists.');
} else {
// handle other errors
}
}
});
}
}
For some reason I am getting this error:ORIGINAL EXCEPTION: No provider for Auth!
ORIGINAL STACKTRACE:
Error: DI Exception
Everything is setup to a tee like the ionic cloud docs suggest : https://docs.ionic.io/services/auth/#setup
I've looked everywhere for this answer
In the setup instructions it talks about how to add the ionic cloud NgModule to your module's imports:
https://docs.ionic.io/setup.html
import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
const cloudSettings: CloudSettings = {
'core': {
'app_id': 'APP_ID'
}
};
#NgModule({
declarations: [ ... ],
imports: [
IonicModule.forRoot(MyApp),
CloudModule.forRoot(cloudSettings)
],
bootstrap: [IonicApp],
entryComponents: [ ... ],
providers: [ ... ]
})
export class AppModule {}
I had missed these steps. Making this change fixed the problem.
Try this
#Component({
templateUrl: 'build/pages/signup/signup.html',
providers: [Auth]
})
Not sure if it works because the ionic docs don't say anything about this, but it seems logical looking at your Error
Passing Auth in providers, starts to show that error in console:
Cannot read property 'config' of undefined