Run a service before application or components load Angular 7 - javascript

I am building an application using Angular 7, I have handled the API calls, the JWT Token authentication system using C#, and also updating the LocalStorage() when necessary, when the user logs in and logs out, and all these are working perfectly.
My problem is I want it to run a login check as a middleware within the application rather than on the lifecycle method - ng.onInit(). How do I go about this?
Is there a way to execute lifecycle events as an entry component or service. That is, before any component loads it is able to check if the user is logged in or not and redirect via Router to a desired page.

Guard is based on the routes... so I think you should prefer a module/service solution.
import { APP_INITIALIZER } from '#angular/core';
then add it as a provider like this :
export function initApp(initService: YourInitService) {
return () => {
initService.Init();
}
}
{ provide: APP_INITIALIZER,useFactory: initApp, deps: [YourInitService], multi: true }

Routing Decisions Based on Token Expiration
If you’re using JSON Web Tokens (JWT) to secure your Angular app (and I recommend that you do), one way to make a decision about whether or not a route should be accessed is to check the token’s expiration time. It’s likely that you’re using the JWT to let your users access protected resources on your backend. If this is the case, the token won’t be useful if it is expired, so this is a good indication that the user should be considered “not authenticated”.
Create a method in your authentication service which checks whether or not the user is authenticated. Again, for the purposes of stateless authentication with JWT, that is simply a matter of whether the token is expired. The JwtHelperService class from angular2-jwt can be used for this.
// src/app/auth/auth.service.ts
import { Injectable } from '#angular/core';
import { JwtHelperService } from '#auth0/angular-jwt';
#Injectable()
export class AuthService {
constructor(public jwtHelper: JwtHelperService) {}
// ...
public isAuthenticated(): boolean {
const token = localStorage.getItem('token');
// Check whether the token is expired and return
// true or false
return !this.jwtHelper.isTokenExpired(token);
}
}
Note: This example assumes that you are storing the user’s JWT in local storage.
Create a new service which implements the route guard. You can call it whatever you like, but something like auth-guard.service is generally sufficient.
// src/app/auth/auth-guard.service.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router) {}
canActivate(): boolean {
if (!this.auth.isAuthenticated()) {
this.router.navigate(['login']);
return false;
}
return true;
}
}
The service injects AuthService and Router and has a single method called canActivate. This method is necessary to properly implement the CanActivate interface.
The canActivate method returns a boolean indicating whether or not navigation to a route should be allowed. If the user isn’t authenticated, they are re-routed to some other place, in this case a route called /login.
Now the guard can be applied to any routes you wish to protect.
// src/app/app.routes.ts
import { Routes, CanActivate } from '#angular/router';
import { ProfileComponent } from './profile/profile.component';
import {
AuthGuardService as AuthGuard
} from './auth/auth-guard.service';
export const ROUTES: Routes = [
{ path: '', component: HomeComponent },
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard]
},
{ path: '**', redirectTo: '' }
];
The /profile route has an extra config value now: canActivate. The AuthGuard that was created above is passed to an array for canActivate which means it will be run any time someone tries to access the /profile route. If the user is authenticated, they get to the route. If not, they are redirected to the /login route.
Note: The canActivate guard still allows the component for a given route to be activated (but not navigated to). If we wanted to prevent activation altogether, we could use the canLoad guard.
more info here

You should check for Guard in angular, especially canActivate Guard: https://angular.io/guide/router
A guard is created like this:
#Injectable({
providedIn: 'root'
})
export class MyGuard implements CanLoad {
constructor() {}
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> |
Promise<boolean> | boolean {
const x = true;
if (x) {
return true; // It allows access to the route;
} else {
// redirect where you want;
return false; // it doesnt allow to access to the route
}
}
}
Then in your routing Module:
{
path: "yourRoute",
canActivate: [MyGuard],
component: YourComponent
}
For authentication, you have a good library that uses guard here:
https://www.npmjs.com/package/ngx-auth

You should implement an authGuardService or something like that to use as middleware for your routing (using the canActivate section)
See: https://angular.io/api/router/CanActivate
This prevents routes from being loaded if the canActivate fails the condition (which is preferred when using a login system etc instead of checking in lifecycle hooks).

Related

Router.navigate not loading the full page content in Angular

I am trying to fix an issue users are having after impersonating as a different user in the system. Currently, a regional user could impersonate as someone else which does not load the full content of the page as they now have to press F5 to see everything on this page. I tried to reproduce this issue by pointing to the same database in local but not able to do so. When I go to the DEV url for instance, I then try impersonating as a different user which then loads the page partially and refreshing the page (F5), I see the entire content. I believe this is happening due to the route navigate, not sure if I am missing anything to pass in this function.
this.router.navigate(['/', user.Company.Abbreviation.toLowerCase()]);
This is the full function that the Impersonate button is executing.
setImpersonatation() {
this.store.dispatch(new AuditSearchClearAction());
this.store.dispatch(new CompanyChangedAction(this.impersonationForm.value.company, null));
const user = this.impersonationForm.value.user as UserLogin;
this.store.dispatch(new BaseUserSetAction(user as User));
this.store.dispatch(new AuthorizeAction(user));
this.router.navigate(['/', user.Company.Abbreviation.toLowerCase()]);
this.store.dispatch(new MyWorkSetLetterReminderUserAction(user.UserId));
}
When I switch a user, I get the screen below.
But when I refresh the page (F5), then i see the entire data.
Do I need to add any parameters to the router.navigate so it loads the page correctly? Seems like something is missing of when trying to load the page after the Impersonate button is clicked.
Thanks!
UPDATE:
setImpersonatation() {
this.store.dispatch(new AuditSearchClearAction());
this.store.dispatch(new CompanyChangedAction(this.impersonationForm.value.company, null));
const user = this.impersonationForm.value.user as UserLogin;
this.store.dispatch(new BaseUserSetAction(user as User));
this.store.dispatch(new AuthorizeAction(user));
this.store.dispatch(new MyWorkSetLetterReminderUserAction(user.UserId));
**this.changeDetector.detectChanges();
this.router.navigate(['/', user.Company.Abbreviation.toLowerCase()]);**
}
Using a resolver is always a good choice in cases that data is not ready to generate the page UI.
Resolver is preventing your app from navigate unless the data is fetched completely from server.
Steps:
create a resolver service by running this command:
ng generate resolver test-resolver
you have to put your ajax call in the resolve method of resolver:
#Injectable({ providedIn: 'root' })
export class TestResolver implements Resolve<Hero> {
constructor(private service: TestService) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
return this.service.testAjax();
}
}
you have to define your resolver in your routing module:
#NgModule({
imports: [
RouterModule.forRoot([
{
path: 'test',
component: TestComponent,
resolve: {
test: TestResolver
}
}
])
],
exports: [RouterModule]
})
export class AppRoutingModule {}
you have to catch the response in your component (in this case TestComponent):
#Component({
selector: 'app-test',
template: ``
})
export class TestComponent {
constructor(private activatedRoute: ActivatedRoute) {
this.activatedRoute.data.subscribe((data) => {
console.log(data.test);
});
}
}
In this way you will navigate to the next page ONLY if data is fully loaded and this feature makes it so much easier to implement a better user experience like a loading on navigation to those pages that require some data to be fetched.

How to access protected routes / disable Auth Guard by knowing auth status

scenario :
Here i am building an application in which currently i have a page called products-info where a url is sent to the customer and on clicking on the link he can directly open that particular product page and see the info and the same page will be there in application.
issue :
Here i am protecting the routes using Auth guards so without logging, a user cannot access the page .If i sent the same page url to user via email he should be able to view only the page.
So my application has:
login page
products page
products-info page
Normally if a user logs in, this page will be visible but when an admin sends a url as mobiledot.com/products-info to user's email, user clicks on that and application don't want to login and it doesn't want to show any logout or other pages info only on that specific page. below is my code :
router.module.ts
const routes: Routes = [
{ path: '', component: LoginComponent },
{ path: 'main/:role', component: MainComponent, canActivate: [RouteGuard] },
{ path: 'admin', component: AdminComponent},
{ path: 'user', component: userComponent,canActivate: [RouteGuard]}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [RouteGuard]
})
export class AppRoutingModule { }
auth guard
#Injectable()
export class RouteGuard implements CanActivate {
constructor(private service: accessService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
const isAllowed = this.service.getAuthenticated();
if (!isAllowed) {
this.router.navigate(['/']);
}
return isAllowed;
}
}
So i also thought about some thing like if user logs into application. My router module is:
if(user loginin){
{ path: 'main/:role', component: MainComponent, canActivate: [RouteGuard] },
}
else {
{ path: 'main/:token', component: MainComponent },
ex: www.mobiledot.com/product-info?toke="ssdsdsdsdSDSD"
}
Is it possible or do we have any other way?
In short, if admin sent the same page url in application which is protected by auth guards to the user via email then user will click on the link and open the page and it should not ask for login.
There is another problem which is about stored token in localstorage. so before moving, do we have to clear that token and place a new one so that it will not redirect to main page?
You mean that products-info should be visible for registered users, as they can receive the corresponding link inside their email while they may be unauthorized (perhaps by token expiration) so you need a solution to cover it.
First of all, this is a bad practice to reveal sth like token as Url parameter. So avoid it.
Second, Please try to employ right solution instead of changing basic behaviors in your App. AuthGuard designed to decide if a route can be activated or not. It uses browser storage and in case of token expiration, You can only refresh it before expiration, otherwise user has to login again.
My solution is creating sth like a ticket id which can be appended to the sent link (like mobiledot.com/products-info/928f5f8b663571b7f3d829921c9079939c2d0319). You can make it one-time valid or more for users. store it in database and validate it when a user uses. In this way, there's no need to employ AuthGuard just control content permission on server-side.
As you have a route guard , you need a case where it's should return true even you are not logged in.
So, simply you can use the approach you have think of www.mobiledot.com/product-info?toke="ssdsdsdsdSDSD".
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
// check if there is a query param , like token in the url and url is your without token one.
if(queryParamToken && urlpath === 'view'){
// as you have token hit the api to check if required to check token is a valid one.
or can simply return true as per your need / requirement
-- checkout this link if you need referece
} else {
const isAllowed = this.service.getAuthenticated();
if (!isAllowed) {
this.router.navigate(['/']);
}
return isAllowed;
}
}
Verify token with API in Angular8 canActivate method of AuthGuard -- checkout this link if you need reference

Angular 8 resolver is not resolved

I have console.log in constructor and ngOnInit() of Resolver but which are not logged.
resolve:{serverResolver:ServerResolverDynamicDataService}},
console.log("ServerResolverDynamicDataService constructor");
console.log('ServerResolverDynamicDataService resolve:'
const appRoutes : Routes =[
{path:"",component:HomeComponent},
{path:"servers",canActivateChild:[AuthGuardService],component:ServersComponent,
children:[
{path:":id",component:ServerComponent,
resolve:{serverResolver:ServerResolverDynamicDataService}},
{path:":id/edit",component:EditServerComponent,canDeactivate:[CanDeativateGuardService]}]},
Resolver:
#Injectable()
export class ServerResolverDynamicDataService implements Resolve<ServerModel>{
constructor(private serversService:ServersService){
console.log("ServerResolverDynamicDataService constructor");
}
resolve(activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot:
RouterStateSnapshot): ServerModel | Observable<ServerModel> | Promise<ServerModel> {
console.log('ServerResolverDynamicDataService resolve:'+activatedRouteSnapshot.params['id']);
return this.serversService.getServer(+activatedRouteSnapshot.params['id']);
}
}
Update1: app.module.ts has entry of this service in providers
providers: [ServersService,AuthGuardService,AuthService,CanDeativateGuardService,ServerResolverDynamicDataService],
Whenever URL(http://localhost:4200/servers/1?allowToEdit=0&allowTest=2#loadPage) is getting hit, no logs are coming from resolver but logs are there in code and the application is properly refreshing if I edit any other part of the application log. So app changes are reflecting the only problem is resolver is not called.
Update2
As per Angular 2+ route resolvers not being called if I remove parent canActivateChild service which is working.B ut I don't know what is wrong.Please help me to understand.
A Resolver is a service and OnInit isn't executed in a service. In fact, besides OnDestroy, there's no other lifecycle hook in a service.
Anyway, I'm assuming your resolver is provided somewhere. If it's not, you should use an argument in its decorator: #Injectable({providedIn: 'root'}).

NestJS Access HTTP request in #ResolveProperty guard?

I'm in the middle of developing a Graphql API with Nest JS (I just upgraded it to 5.0.0), in which I need to allow only certain users (by roles) to access sub-parts of queries that are public.
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) // if no roles specified
return true;
console.log(req); // Displays Query() parent result instead of HTTP Request Object
return false;
}
}
Sub-parts of queries are handled by #ResolveProperty() decorated functions.
Following the documentation I started using Guards, but I just found out that when using guards, in GraphQL resolvers context, the context.switchToHttp().getRequest() is only returning the result of the parent Query()...
I don't really know if this is an expected behavior, and if it is, if there's a way to access full express Request object from a GraphQL Guard ?

Angular2 run Guard after another guard resolved

In my project I have two guards. AuthGuard and PermissionGuard. I need to first AuthGuard runs and when it resolved and if true the permissionGuard begins but now this guards are running parallel and permissionGuard not working well. the way I used for this issue is that I called the AuthGuard CanActivate method in Permission guard but I think there is a quite better way for doing this.
The best way I've seen it done is to expose the router guards on child routes. Here is a working example.
{
path:'', canActivate:[AuthorizationService1],
children: [
{
path:'', canActivate:[AuthorizationService2],component: HomeComponent
}
]
}
guards can't really depend on each other unfortunately. you can do as another person suggested and make one on the parent route and one on a child route, or My preference is just to make a third guard that runs both checks on it's own sequentially so I don't need to muddy up my routes, assuming they're implemented as observables:
#Injectable({ providedIn: 'root' })
export class AuthAndPermissionGuard implements CanActivate {
constructor(private authGuard: AuthGuard, permGuard: PermGuard) { }
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authGuard.canActivate(next, state).pipe(
switchMap(isAuthed => isAuthed ? this.permGuard.canActivate(next, state) : of(false))
);
}
}
you can esaly pass two gurd to routes like this :
{
path:'', canActivate:[AuthorizationGuards1,AuthorizationGuards2]
}
after AuthorizationGuards1 success AuthorizationGuards2 activated, the relation between these two guard is st like AuthorizationGuards1 && AuthorizationGuards2 conditions.

Categories