Show login modal on unauthorized response angular 7 - javascript

I need to show a login modal every time the server returns a http unauthorized status (401), and in that case, stop the page loading... for example, I'm logged in but trying to access an protected resource that only admin users can do it.. so in that case I would like to show an modal with login and password to the user. It could be on navigating to a protected route or on delete event for example.
I tried to do it in an ApiInterceptor:
#Injectable({providedIn: 'root'})
export class ApiInterceptor implements HttpInterceptor {
constructor(
...
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({ url: environment.baseUrl + req.url });
if (this.authService.validToken) {
req = req.clone({ headers: req.headers.set('Authorization', `Bearer ${this.authService.validToken}`) });
}
if (!req.headers.has('Content-Type')) {
req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
}
return next.handle(req).pipe(catchError(resp => this.handleError(resp)));
}
private handleError(httpError: HttpErrorResponse) {
if (httpError.status === this.UNAUTHORIZED) {
// opening login modal here, but can't stop the request to prevent user to se unauthorized data, and after login, how can I redirect user to the same resource he tried to access?
}
return throwError(httpError);
}
}
Need help here, if someone have an idea in how to do it will be appreciated!

Your ApiInterceptor looks like it's for adding a bearer token to the request. I'd call this the TokenInterceptor or similar, and create a new one for handling unauthorised requests.
I'd create a new interceptor and call this UnauthorisedRequestInterceptor. Something similar to this:
#Injectable({ providedIn: 'root' })
export class UnauthorisedRequestInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
map(event => {
return event;
}),
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
this.router.navigate(['/auth/login']);
}
return throwError(error);
})
);
}
}
This will intercept every http request, and if the returned status is 401, it will redirect you to your login page.
Then add this into your list of providers in app.module.ts:
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UnauthorisedRequestInterceptor,
multi: true
}
]
As for redirecting users on access to a protected route, this should be done in your auth guard.

Every protected data that need roles or permissions should be on the server & have specific authorization to access it, if there is already protected data on your application you should consider moving it onto your server or add at least a guard.
If you want to redirect the user after a 401 you have to inject the Router service in your interceptor & use the navigate function (cf this.router.navigate(['/myRoute']); )

Related

How to handle the Internal server error? Nestjs

How to handle the error so that if the user does not provide a token, then an UnauthorizedException is thrown.
At the moment I am getting this error:
{
"statusCode": 500,
"message": "Internal server error"
}
ts:
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
try {
const jwt = request.headers.authorization.split(' ')[1];
if (!jwt) {
throw new UnauthorizedException('Token is not provided.');
}
return this.jwtService.verify(jwt);
} catch (e) {
return false;
}
}
You can try to recreate auth module from the documentation.
Or try to console.log() on each line.
By default, JWT internal module works well. It can encode and decode all that you need automatically.
https://docs.nestjs.com/security/authentication
I use a middleware for this purpose. I'll share a basic version of it.
auth-middleware.ts
import {HttpStatus,Injectable,Logger,LoggerService,NestMiddleware,} from '#nestjs/common';
import { NextFunction } from 'express';
import { Request, Response } from 'express';
#Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService: AuthService,
// (I use Firebase auth. You can inject JWT service here instead)
private readonly logger: LoggerService, // Good'ol logger
) {}
public async use(req: Request, res: Response, next: NextFunction) {
// Checks if req has authorization header
const header = req.headers['authorization'];
if (!header) {
// If no headers are present, returns a 401.
// I use problem+json
// Thats why you are seeing more fields in the response instead of just a
// code and message
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Splitting "Bearer token" to ["Bearer","token"]
const token = header.split(' ')[1];
// Validating token with auth service
// It returns a "tuple" for me...you can have it return whatever you want
const [
authClaims, // Custom claims that is extracted from the JWT
result, // JWT Validation result (boolean)
authProviderUid, // Firebase UID
] = await this.authenticationService.verifyToken(token);
if (
!result || // If JWT is invalid
authClaims.accountStatus === AccountStatusList.Banned ||
authClaims.accountStatus === AccountStatusList.Suspended
) {
// You shall not pass
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Attaches the claims, result and UID with req for the next middleware(s)/controller
req['authResult'] = { authClaims, result, authProviderUid };
//Reassuring
this.logger.log('Token verified', AuthMiddleware.name);
// next function from express
next();
}
}
Next, In the module(s) your controllers are declared,
api.module.ts
import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '#nestjs/common';
#Module({
imports: [
//...
],
controllers: [
AuthController,
ProfileController,
SubscriptionController
],
providers: [
//...
],
})
export class ApiModule implements NestModule {
public async configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
// Exclude some paths
.exclude({ path: '/api/v1/auth/sign-up', method: RequestMethod.POST })
.forRoutes( // Your controller classes you want to run the middleware on
ProfileController,
SubscriptionController,
AuthController
);
}
}
How it works
Every request goes through the specified middleware (if path not excluded). If the request is unauthorized, throws an error before it reaches the controller.
If the request is at the controller, the request is authenticated. You have to take care of the authorization part with guards etc...
Authentication and Authorization are different.
I'd suggest to use middleware for authentication and guards for authorization.
Links :
NestJS Middleware Documentation
Problem Details

Angular Auth Guard after Login does not get observable informations

I implemented a login in angular.
It works well, but I like to redirect after successfully authentication.
The redirect does not work after receiving the user from server.
If I refresh my page and use the login, it works.
I receive an object from my webservice and save it to "localStorage".
I use a BehaviourSubject to observe my current user.
If I press 'login' i run into auth guard, but the auth guard does not know the user. (its null)
What is my mistake?
Auth.guard.ts
#Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthService
) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log(this.authenticationService.currentUserValue);
if (this.authenticationService.currentUserValue) {
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/entrance/login'], { queryParams: { returnUrl: state.url } });
return false;
}
}
#Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUserSubject: BehaviorSubject<User>;
constructor(private http: HttpClient) {
this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
}
public get currentUserValue(): User {
return this.currentUserSubject.value;
}
login(username: string, password: string) {
return this.http.post<User>('/api/auth', { username, password }).pipe(map(user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
this.currentUserSubject.next(user);
}
return user;
}));
}
this.authService.login(email, password).pipe(first()).subscribe(data => {
this.router.navigate([this.returnUrl]);
});
I like to redirect directly after login without refresh.
You see any mistakes?

Refreshing token for parallel HTTP requests using HttpInterceptor

I'm working on an Ionic app and trying to cash in the refresh token when a user gets a 401 response on an HTTP request. I found a few examples floating around online and was able to get this one (https://www.intertech.com/Blog/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/) working with the exception of multiple requests coming in at once.
The problem I'm having is the first call in the series of calls invokes the refresh token and retries successfully, while the other ones never get retried. If I take the .filter and .take off the subject return for requests where a refresh is already in progress, the calls do get retried but without the new token. I'm pretty new when it comes to observables and subjects so I'm not really sure what the problem could be.
requests
this.myService.getData().subscribe(response => {this.data = response.data;});
this.myService.getMoreData().subscribe(response => {this.moreData = response.data;});
this.myService.getEvenMoreData().subscribe(response => {this.evenMoreData = response.data;});
interceptor
#Injectable()
export class HttpInterceptor implements HttpInterceptor {
isRefreshingToken: boolean = false;
tokenSubject = new BehaviorSubject<string>(null);
tokenService: tokenService;
constructor(private authService: AuthService, private injector: Injector) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
return this.authService.getUser().flatMap(user => {
request = this.addToken(request, next, user.accessToken);
return next
.handle(request)
.catch(error => {
if (error instanceof HttpErrorResponse) {
switch ((<HttpErrorResponse>error).status) {
case 401:
return this.handle401(request, next, user);
}
} else {
return Observable.throw(error);
};
})
});
}
addToken(request: HttpRequest<any>, next: HttpHandler, accessToken: string): HttpRequest<any> {
return request.clone({ setHeaders: { Authorization: 'Bearer ' + accessToken }})
}
handle401(request: HttpRequest<any>, next: HttpHandler, user: any) {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
this.tokenSubject.next(null);
this.tokenService = this.injector.get(tokenService);
return this.tokenService.refresh(user.refreshToken)
.switchMap(refreshResponse => {
if (refreshResponse) {
this.authService.setUser(refreshResponse.id_token, refreshResponse.access_token, refreshResponse.refresh_token);
this.tokenSubject.next(refreshResponse.accessToken);
return next.handle(this.addToken(request, next, refreshResponse.access_token));
}
else {
//no token came back. probably should just log user out.
}
})
.finally(() => {
this.isRefreshingToken = false;
});
}
else {
return this.tokenSubject
.filter(token => token != null)
.take(1)
.switchMap(token => {
return next.handle(this.addToken(request, next, token));
});
}
}
}
It looks to me like you didn't have the right token:
You had:
this.tokenSubject.next(refreshResponse.accessToken);
Should be:
this.tokenSubject.next(refreshResponse.access_token);
I actually ended up solving this by moving the subject to my auth service and doing a next in the setUser method. Then in the else statement in my 401 method, I returned the subject from a new method on my auth service and that fixed it. I still needed the take(1) but was able to get rid of the filter since I ended up not using a BehaviorSubject.
I faced a similar issue in the past. For some unknown reason (at least to me), when I intercept the 401, I make the refresh and retry, but retry operation goes cancelled.
Nevertheless, I realised that I can read the JWT expiration on client-side, so I tricked the system by saving the token expiration time. I then made routing events (say onViewWillEnter) check the expiration and, if token expired, refresh it.
This mechanism is totally transparent to the user, ensures that auth token nor refresh token expire if the user stays too long without performing HTTP requests and, most importantly, reduces latencies as you never get a 401 response (which, in your scenario, translates to three requests).
One simple way to achieve this is by means of a guard:
canActivate(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
if (this.refreshTokenService.isExpired) {
this.tokenEvent_.next();
return false;
} else {
this.refreshTokenService.refresh();
}
where refreshTokenService is a utility service that has the tokens and a method for performing refresh via HTTP. tokenEvent is a rxjs/Subject: it is subscribed in guard constructor and each time a new event comes, it redirects to login page.
Adding this guard on every route ensures that the token is always non-expired.

Angular 4: How to show Re-login Dialog/Modal from intercepter

Problem statment
I am very new to Angular 4 and struggling to find out how to get a user re-logging when the token expires.
Lets dig into code
I have an response intercepter that checks the response code for 401 error
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
return next.handle(request).do(
// success responses
(event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// I do not want to do anything here... just pass
}
},
// error responses
(err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
//here is where I need to show a modal
//OH! STACKOVER-FLOW PLEASE BLESS ME
}
}
});
}
Just Informing
The application is too modular as every component is a module itself. Like for an example : Login Component is a module itself and Registration is Another module which are included in a the root module using routes...
So could you please help me with the best practice to solve this riddle?
I'm using Angular4 CanActivate to check whether user logged in or not, I think it would works the same way as your approach.
Anyway in your canActivate service or inside of your hook I can see 2 solutions:
1) as #Sajal mentioned - broadcast event:
#Injectable()
export class YourService {
heyStopRightThere: EventEmitter<boolean> = new EventEmitter();
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
...
if (err.status === 401) {
this.heyStopRightThere.emit();
}
...
}
}
and then in all secured components
constructor(
private _yrSvc: YourService
) {
}
showLoginDialog() {
//enable component LoginDialog that embeded in
// <loginDialog *ngIf="notLoggedIn"></loginDialog>
}
ngOnInit() {
this._yrSvc.heyStopRightThere.subscribe(() =>
showLoginDialog()
);
}
2) Redirect with param to callback:
#Injectable()
export class YourService {
constructor(private router: Router){}
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
...
if (err.status === 401) {
this.router.navigate(['/login', {callback: location.href}]);
}
...
}
And then your Login component you can draw your dialog and redirect back to "callback" on success.

Angular 2 Facebook Authentication

I´m triying to implement facebook authenticathion from my angular 2 app but I think I'm implenting in the wrong way and I can't find some tutorials about this and also I want to ask if ¿is Auth0 the only way to implement this?.
Well I have this:
Node JS server :
// FB authentication
authRoutes.get('/facebook', passport.authenticate('facebook', { session : false, scope: ['email'] }))
authRoutes.get('/facebook/callback', passport.authenticate('facebook', { session : false, failureRedirect: '/'}), ctrlAuthentication.facebookResponse)
these are the routes in the api and I'm using passportjs, all works when I call the api directly in the browser like: localhost:3000/api/auth/facebook and I get the user returned from the api in JSON format.
Now in the front I have angular 2 and this is
authentication.service.ts:
import {User} from '../_models/user'
#Injectable()
export class AuthenticationService {
public token : String;
public user : User;
constructor(private http: Http){
}
fbLogin() : Observable<User> {
let user = this.http.get('/api/auth/facebook')
.map(this.mapUser);
return user;
}
mapUser(response : Response) : User {
console.log(response)
let data = response.json();
this.token = data.token;
let user = <User>(data.user);
this.user = user;
localStorage.setItem('currentUser', JSON.stringify({ user: user, token: data.token }));
return user;
}
}
and this is the register.component.ts:
import {AuthenticationService} from '../_services/authentication.service'
import {User} from '../_models/user'
#Component({
selector: 'crowd-register',
templateUrl: 'app/RegisterComponent/register.component.html'
})
export class RegisterComponent {
private user : User;
constructor(
private authenticationService : AuthenticationService
) {}
fbLogin() {
this.authenticationService.fbLogin().subscribe(
user => {
this.user = user;
}
)
}
}
When I call fbLogin() from the app I get this error:
SyntaxError: Unexpected token < in JSON at position 0
MapSubscriber.AuthenticationService.mapUser [as project] (authentication.service.ts:46)
I think this is because I'm don't getting the user from my api, I'm getting the response from facebook and this isn't executing or something.
This is the picture of the console.log(response) and the error.
Thanks for all answers, sorry for my english.

Categories

Resources