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

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

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.

What is a proper way to preserve parameters after navigating back?

In my Angular app, I have list and details pages and I want to lkeep the pageIndex value before navigating to details page. There is a Back button in the details page and I can return the list page by clicking on that button. However, I want to get the pageIndex value when navigating back to the list page and let the user open the page where he/she was before. For example I navigate 3rd page on the list and click details. At this stage I set the pageIndex to 3 and then navigate to details. Then by clicking the Back button I can navigate back to the list page, but I need to retrieve the pageIndex as 3.
So, what is an elegant way to fix this problem in Angular 10+?
list-componnet
details(id) {
this.router.navigate(['/details'], { state: { id: id } }); // I pass id value of the record
}
details-componnet
constructor(private router: Router) {
this.id = this.router.getCurrentNavigation().extras.state.id;
}
back() {
this._location.back();
}
Just write a simple example to make it work, I use the sessionStorage and router together, use router to show your the routing module, actually you can just use sessionStorage only, and wrapper it in a servive. Then you can retrieve the pageIndex anywhere.
And if you want to use router only, the pageIndex paramater will be place in both list and detail component, since this two component all need to use this value, in list component you will need pageIndex to set data-table, in detail component you need this value to pass to list component when redirect back triggered.
The routing module like below:
import { NgModule } from "#angular/core";
import { Routes, RouterModule } from "#angular/router";
import { ListComponent } from "./list/list.component";
import { DetailComponent } from "./detail/detail.component";
const routes: Routes = [
{ path: "", redirectTo: "list", pathMatch: "full" },
{
path: "list/:pageIndex=1",
component: ListComponent,
pathMatch: "full"
},
{
path: "detail/:id",
component: DetailComponent
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Here you can navigate to list page from detail page use:
this.router.navigate(["/list/" + pageIndex]);
And then in list page's ngOnInit method to set current pageIndex to your data-table. Here is the demo: https://stackblitz.com/edit/angular-ivy-5vmteg?file=src/app/list/list.component.ts
Use sessionStorage, a listService or router queryParams to keep track of the current pageIndex.
I´d advocate for queryParams as it seems most logical and you can also link directly to specific page.
constructor(
private route: ActivatedRoute,
) { }
// Access the queryParam in list component
// Ie /list?pageIndex=4
this.route.queryParams.subscribe(params => {
// Do something with params.pageIndex if it exists
}
);
I´d also consider to change the way you handle routing to the details. If the route to the list is /list then route to the details should be /list/<listid> so you can link directly to the details if needed.
You can access the listId parameter as below but note it must also be specified as parameter in the router definition.
// Router definition
{ path: 'list/', component: ListComponent},
{ path: 'list/:listId', component: ListIdComponent}
// Access the listId in the details component
this.route.params.subscribe(param=> {
// Do somthing with param.listId
});

Run a service before application or components load Angular 7

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).

Invalid url in Angular should check some condition before redirecting

My initial domain is localhost:4200 and which in redirects me to some page, based on my routing, but I want to input an invalid domain for example localhost:4200/Whasup or localhost:4200/Whasdown, where Whasup and whasdown page doesn't exist. Before i get redirected to default Angular 404 page, I need to check some conditions. And if my condition doesn't satisfy then i want to redirect it to 404 page else i want to handle it.
my app-routing.module.ts (Auth Guard Checks if the user is logged in or Not)
const routes: Routes = [
// Fallback when no prior route is matched
// Shell.childRoutes([]),
// { path:'home', component: HomeComponent},
// { path: '**', redirectTo: '', pathMatch: 'full' }
Shell.childRoutes([
{ path: '', component: RedirectToComponent, canActivate: [AuthGuard] },
{
path: 'dashboard',
loadChildren: 'src/app/dashboard/dashboard.module#DashboardModule',
canActivate: [AuthGuard]
},
{
path: 'exec-dashboard',
loadChildren:
'src/app/exec-dashboard/exec-dashboard.module#ExecDashboardModule',
canActivate: [AuthGuard]
},
{
path: '**',
redirectTo: '',
canActivate: [AuthGuard]
}
]),
{
path: 'auth',
loadChildren: 'src/app/auth/auth.module#AuthModule',
canActivate: [AuthGuard]
}
];
#NgModule({
imports: [
RouterModule.forRoot(routes, {
enableTracing: false, // Enable for debug purpose.
useHash: true,
preloadingStrategy: PreloadAllModules
})
],
exports: [RouterModule],
providers: [AuthGuard]
})
I have also implemented KeyCloak Angular interceptor which helps me get to the keycloak login page, if user is not logged. I have providers defined in app.module.ts.
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializer,
multi: true,
deps: [KeycloakService]
}
],
So whenever I hit my initial domain(localhost:4200), intializer function gets called before i get redirected to any page, same should happen with invalid sub-domain url(localhost:4200/Whasup).
Edit: Can i implement above mentioned by any chance, or can i implement sub domain logic in angular, for example, tenant1.localhost:4200 or tenant2.localhost:4200 , (when deployed it would be tenant1.mycompany.com). Can someone suggest me a way to implement sub domain routing in angular.
Thanks Much.
I will post here as my explanation will not fit in a comment. I'm not sure if this will necessarily answer your question as there are a number of things to consider. So here it goes...
Note
If you want your application to work with subdomains you cannot use localhost together with subdomains (like tenantName.localhost:4200) to test locally, you'll have to setup custom domains using the hosts file. You can see an example of this here. Personally I prefer having a /tenantname rather than a subdomain, but that's personal preference and based on your business requirements I guess.
Handling Redirections
Whether working with subdomains or the client name as part of the URL, you should be able to handle the "automatic redirection" to the "tenant URL" after the user is logged in. I am assuming that after a successful login as part of the response you will have the tenant name. Once you have the tenant name you can easily redirect the user to the correct URL.
I am mentioning this because in your last comment you stated that on application start you want to redirect to a "tenant specific" URL. Important: Without the user being logged in you cannot know which URL to redirect to.
Now if the user is logged in and they access the website? Well in the AuthGuard you can have a condition that checks if the user is logged in, and if yes redirect the user to the correct tenant URL. Again this is me assuming that as part of the login response you have some information that allows you to know the tenant that the user belongs to.
In short I would suggest:
Make sure that the response of a successful authentication returns some information about the tenant.
Using the tenant information in your authentication response, you can handle redirect the user to the correct tenant URL from the Angular app.
Also keep in mind that you need to handle cases when a user tries to access a URL of a tenant that they do not have access to.

How to keep query string parameters in URL when accessing a route of an Angular 2 app?

I have an Angular 2 test app running the latest alpha (37). There are just three routes, that look like this:
#RouteConfig([
{ path: '/', component: Home, as: 'home' },
{ path: '/errors', component: Errors, as: 'errors' },
{ path: '/about', component: About, as: 'about' }
])
I can access the routes, and when I place query string params in the URL I can read them just fine. However, a few instants after I read the parameters, I noticed that the route loads fine and the URL refreshes, removing the query string parameters.
In other words, accessing this route:
http://localhost:5555/errors?test=abc
Loads the /errors route, but right after the application loads the URL turns into:
http://localhost:5555/errors
That would be confusing to a user. I would like to keep the query string parameters in the URL.
I know, it's in alpha, but is this a bug or I forgot to set something up on the router? How can I keep whatever query string parameters in the URL?
Thanks!
This is fixed in Alpha 41. The query string is now persisted.
Updating for Angular 2:
To preserve query parameters present in the current url, add
// import NavigationExtras
import { NavigationExtras } from '#angular/router';
// Set our navigation extras object
// that contains our global query params
let navigationExtras: NavigationExtras = {
preserveQueryParams: true
};
// Navigate to the login page with extras
this.router.navigate(['/someLink'], navigationExtras);
For more info, check here:
Try this
this.router.navigate(['target'], {preserveQueryParams: true});

Categories