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

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.

Related

Redirecting when error status 401 in angular

in Angular project I have a reset password page, but when I want to restore via the link from mail, it constantly redirect me to the login page because it requires authorization. I solved this problem like this, but I have another problem that when the time of the token expire, it does not redirect to the login page.`
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
tap(
() => {},
(err: any) => {
// if (!request.url.includes(environment.i18nUrl)) {
if (err.status === 401 && this.route.snapshot.url[0].path === 'reset-password') {
this.authService.logout();
} else if (err.status === 403) {
let error = new ErrorModel();
error.errorType = ErrorType.Error;`

You provided 'undefined' where a stream was expected. in token interceptor

I am trying to make an interceptor to refresh the token, but it throws me this error and I don't know why
ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
token-interceptor.service.ts
import { Injectable } from '#angular/core';
import { AuthService } from './auth.service';
import { HttpClient, HttpErrorResponse, HttpHandler, HttpInterceptor, HttpRequest } from '#angular/common/http';
import { environment } from 'src/environments/environment';
import { catchError, map} from 'rxjs/operators';
import { throwError } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class TokenInterceptorService implements HttpInterceptor {
constructor(
private auth: AuthService,
private http: HttpClient
) { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(
catchError((err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.url.includes('signin') || err.url.includes('refreshToken')) {
return next.handle(req)
}
//if error is not about authorization
if (err.status !== 401) {
return next.handle(req)
}
this.renewToken(req).subscribe(request => {
return next.handle(request)
})
} else {
return throwError(err)
}
})
)
}
renewToken(req: HttpRequest<any>) {
return this.http.get(`${environment.API_URL}/refreshToken`, { withCredentials: true }).pipe(
map((res: any) => {
//update access token
this.auth.setToken(res.token)
return req.clone({
setHeaders: {
authorization: `Bearer ${res.token}`
}
})
})
)
}
}
Ignore this: It looks like your post is mostly code; please add some more details. It looks like your post is mostly code; please add some more details.
this piece of code is wrong:
this.renewToken(req).subscribe(request => {
return next.handle(request)
})
istead it should be:
return this.renewToken(req).pipe(switchMap(request => next.handle(request)));
you are just returning nothing in your variant, that is why it doesn't work.
also the whole logic of token interpceptor seems weird to me. I believe you should rethink about how you want it to work. for now as I see you sending request without token and in almost all cases you are sending it again unmodified, and the one that I fixed above will send it again with token. Wouldn't it be right to add token every time, and only send it 2nd time if token is outdated?

Show login modal on unauthorized response angular 7

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']); )

AngularFirebaseAuth : Calling server api just after firebase auth?

My auth is based on 2 things :
firebase auth (email/password)
call on a server API to retrieve full customer entity from BDD and from firebaseID (user must exists)
So a user will be "authenticated" if these two conditions are met.
I also have authGuards based on a isAuthenticated() returning an Observable (because on a page refresh, guard must wait for the auth to be finished before redirecting the user anywhere).
Problem : I can't find a way to make that work with all the async and rxjs mess/hell .. Currently it's working but each time isAuthenticated is called, the serverAPI auth is called every time...
How can I refactor that in order to call server only once and all the async/reload stuff still works ?
AuthService :
export class AuthService {
public userRole: UserBoRole;
public authState$: Observable<firebase.User>;
constructor(
private afAuth: AngularFireAuth,
private snackBar: SnackBarService,
private translate: TranslateService,
private router: Router,
private grpcService: GrpcService
) {
this.authState$ = this.afAuth.authState.pipe(
take(1),
mergeMap(user => {
if (!user) {
return of(user);
}
// User is successfully logged in,
// now we need to check if he has a correct role to access our app
// if an error occured, consider our user has not logged in, so we return null
return this.checkProfile().pipe(
take(1),
map(() => {
this.test = true;
return user;
}),
catchError(err => {
console.error(err);
return of(null);
})
);
})
);
// Subscribing to auth state change. (useless here because access logic is handled by the AuthGuard)
this.authState$.subscribe(user => {
console.log('authState$ changed :', user ? user.toJSON() : 'not logged in');
});
}
checkProfile() {
return this.callAuthApi().pipe(
map((customer) => {
if (!customer || customer.hasRole() === "anonymous") {
return Promise.reject(new Error(AuthService.AUTH_ERROR_ROLE));
}
this.userRole = customer.getRole();
})
);
}
isAuthenticated(): Observable<boolean> {
return this.authState$.pipe(map(authState => !!authState));
}
}
AuthGuard :
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthService, private router: Router) {}
check(): Observable<boolean> {
return this.authService.isAuthenticated().pipe(
catchError(err => {
// notifying UI of the error
this.authService.handleAuthError(err);
// signout user
this.authService.signOut();
// if an error occured, consider our user has not logged in
return of(false);
}),
tap(isAuthenticated => {
if (!isAuthenticated) {
// redirecting to login
this.router.navigate(['login']);
}
})
);
}
canActivateChild(): Observable<boolean> {
return this.check();
}
canActivate(): Observable<boolean> {
return this.check();
}
}
Thanks
You can change your checkProfile() function to return observable instead of observable from http request or promise in case of error. First you will check if the user already authenticated(I assumed that userRole will be fine since you save it after call to back end) and if yes return a newly created observable without call to your back end, otherwise you will make a request and emit your observable based on result of http call. With next example you will make call only once:
checkProfile() {
return new Observable((observer) => {
if (this.userRole) {
observer.next();
observer.complete();
} else {
this.callAuthApi().pipe(
map((customer) => {
if (!customer || customer.hasRole() === "anonymous") {
observer.error(new Error(AuthService.AUTH_ERROR_ROLE));
observer.complete();
}
this.userRole = customer.getRole();
observer.next();
observer.complete();
})
);
}
});
}
Haha, ReactiveX is not easy one. It has a quite steep learning curve.
But it is really powerful.
1. call server only once
You can use shareReplay.
To understand how shareReplay works, have a look here https://ng-rxjs-share-replay.stackblitz.io
//shareReplay example
ngOnInit() {
const tods$ = this.getTodos();
tods$.subscribe(console.log);// 1st sub
tods$.subscribe(console.log);// 2st sub
}
getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.url)
.pipe(
tap(() => console.log('Request')),
shareReplay(1) // compare with comment and uncomment
);
}
Output with shareReplay
Request
[Object, Object, Object]
[Object, Object, Object]
Output without shareReplay
Request
[Object, Object, Object]
Request
[Object, Object, Object]
You may use shareReplay in your auth service code.
//auth.services.ts
import { shareReplay } from 'rxjs/operators';
...
this.user$ = this.afAuth.authState.pipe(
tap(user => {
console.log('login user$ here', user)
}),
switchMap(user => {
if (user) {
//do something
return this.db.object(`users/${user.uid}`).valueChanges();
} else {
return of(null);
}
}),
shareReplay(1) //**** this will prevent unnecessary request****
);
2. async and await
toPromise()
//auth.service.ts
...
getUser() {
return this.user$.pipe(first()).toPromise();
}
//auth.guard.ts
...
async canActivate(next: ActivatedRouteSnapshot
, state: RouterStateSnapshot
): Promise<boolean> {
const user = await this.auth.getUser();
//TODO your API code or other conditional authentication here
if (!user) {
this.router.navigate(['/login']);
}
return !!user;
}
Hope this will help you.

Where is the 'req' parameter defined?

I'm trying to implement an authentication scheme described here.
I'm struggling to find where the req parameter is defined in the code below. My code would not compile as it is not currently defined. This could be a typo in his code. I looked through the comments but nobody seems to have pointed that out:
// src/app/auth/jwt.interceptor.ts
// ...
import 'rxjs/add/operator/do';
export class JwtInterceptor implements HttpInterceptor {
constructor(public auth: AuthService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}, (err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// redirect to the login route
// or show a modal
}
}
});
}
}"
Can someone point out what i'm missing?
Many thanks in advance.
Looks like a typo to me. The intercept function provides a parameter request - it should probably be referring to that instead of req.
The parameter must read as request as below
return next.handle(request)
.do(event => {
if ()

Categories

Resources