Role based redirecting - javascript

I want to redirect user depending on his role. F.e. When user entered as 'Teacher', he is redirected on page '1'; if he entered as 'Student', he is redirected on page '2'. It has also to work when user inputs only main url 'app.com' (F.e.)
const routes: Routes = [
{
path: '',
component: TesLandingPageContainer,
canActivate: [AutheticatedGuard],
canActivateChild: [UserRoleGuard],
children: [
{
path: '',
redirectTo: '/users/profile',
pathMatch: 'full',
},
...
},
{
path: '**',
redirectTo: ''
}
];
I have this guard
#Injectable()
export class AutheticatedGuard implements CanActivate {
constructor(private store: Store<AppState>, private router: Router) { }
canActivate(): Observable<boolean> {
return this.store.pipe(
select(getIsAuthenticated),
filter(isAuth => isAuth != null),
tap(isAuth => {
if (!isAuth) {
this.router.navigate(['/login']);
}
}),
take(1)
);
}
}
If I write so, it loops
canActivate(): Observable<boolean> {
return this.store.pipe(
select(getUserRoleAndAuth),
filter(user => user.isAuth != null),
tap(user => {
switch (user.role) {
case 'ADMIN': {
this.router.navigate(['user/requests']);
break;
}
case 'TEACHER': {
this.router.navigate(['groups']);
break;
}
case 'STUDENT': {
this.router.navigate(['student']);
break;
}
default: {
this.router.navigate(['/login']);
}
}
}),
map(user => user.isAuth),
take(1)
);
```

Instead of using this.router.navigate(['student']); use return this.router.parseUrl('/student');
parseUrl will create UrlTree which help to navigate application from CanActivate.
you need to return somthing from CanActivate interface , either boolean or UrlTree.
As per your logic return either student or teacher URL.

Related

Load data first before AuthGuard called in Angular

I am currently implementing Auth guard in my routes. I want to load data first before auth guard being called. Currently I am doing this.
In my auth-guard.service.ts I have this.
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if ( this.authService.isLoggedIn() ) {
return true;
} else {
this.router.navigate(['login']);
return false;
}
}
And in my auth.service.ts
export class AuthService {
constructor() { }
isLoggedIn() {
return this.getUser() !== null;
}
setSession(userId: string) {
sessionStorage.setItem('userId', userId);
}
getUser() {
return sessionStorage.getItem('userId');
}
}
And in my routes I am calling Auth guard and DataResolver
const routes: Routes = [
{
path: '',
component: UserComponent,
canActivate: [AuthGuardService],
resolve: {
data: DataResolver
},
children: [
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full',
},
{
path: 'dashboard',
component: UserDashboardComponent,
data: {
breadcrumb: 'Dashboard',
},
}
]
]
DataResolver
#Injectable({
providedIn: 'root'
})
export class DataResolver {
constructor(private authService: AuthService,
private activatedRoute: ActivatedRoute) { }
resolve() {
this.activatedRoute.queryParams.subscribe((data) => {
const userId = data.userId;
if (userId) {
this.authService.setSession(userId);
}
});
}
}
Why it is still calling the auth guard first. What I want to to call the DataResolver first before the auth guard.

Redirect the user before component gets loaded

I would like to redirect the user to the homepage if there is no cookie set.
My problem is that the user may be redirected but it takes too long. For about 1 second they are still able to see the protected page. How can I prevent this?
ngOnInit() {
this.hasAccess().then(result => {
if (!result) {
this.router.navigate(['/']).then();
}
});
}
private hasAccess() {
return new Promise(resolve => {
this.login.hasCookie(this.cookieService.get('login')).subscribe(hasCookie => {
if (hasCookie === 1) {
return new Promise(() => {
this.login.getCookie().subscribe(cookieFromServer => {
if (cookieFromServer === this.cookieService.get('login')) {
return resolve(true);
} else {
return resolve(false);
}
});
});
}
return resolve(false);
});
});
I have also tried to run my function hasAccess() in constructor() instead of ngOnInit() but the problem remains the same.
You should use route guards. They check if the routes can be visited or not before the route loads its content.
#Injectable()
export class CookieGuard implements CanActivate {
constructor(
private cookieService: CookieService,
private login: LoginService
) {}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.login.hasCookie(this.cookieService.get('login'));
}
}
From your notes it is not clear what this.login is, you'll need to adjust that to fit your setup. Then you can configure your route with the following guard:
const routes: Route[] = [
{ path: '/whatever', component: WhateverComponent, canActivate: [CookieGuard] }
];
You can use Angular gurad.
#Injectable({
providedIn: 'root'
})
export class YourGuard implements CanActivate {
path: ActivatedRouteSnapshot[];
route: ActivatedRouteSnapshot;
constructor(private auth: AuthenticationService, private router: Router) { }
canActivate() {
if (your logic) {
....
}
else redirect to ...
}
in your routing.module :
{
path: 'your path',
loadChildren: () => import('path')
.then(m => m.YorModule),
canActivate:[YourGuard]
},
Use a RouteGuard like this:
export class ComponentRouteGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (this.isCookieSet) {
return true;
} else {
this.router.navigate([`unauthorized`]);
return false;
}
}
}
Routes:
export const routes: Routes = [
{ path: 'test', component: YourComponent, canActivate: [ComponentRouteGuard]},
{ path: 'unauthorized', component: UnAuthorizedComponent }
];

redirectTo not working when using canActivate guard

The redirectTo property isn't working in my Angular 2 app. I have the following routes in my app.routing.ts:
const routes: Routes = [
{ path: '', redirectTo: '/page/1', pathMatch: 'full' },
{ path: 'page', loadChildren: 'app/modules/page/page.module#PageModule' }
]
export const routing = RouterModule.forRoot(routes);
Then, in my page.routing.ts, I have the following:
const pageRoutes: Routes = [
{ path: ':id', component: PageComponent, canActivate: [LoginGuard] }
];
export const pageRouting = RouterModule.forChild(pageRoutes);
Every time I access the home page it displays the LoginComponent for a second, then it disappears. However, it should redirect to the PageComponent.
Why isn't that happening? Why the LoginComponent is being loaded (even if it's only for a brief second) if the user is already logged in?
Here's my LoginGuard:
#Injectable()
export class LoginGuard implements CanActivate {
constructor(private af: AngularFire, private router: Router) {}
canActivate(): Observable<boolean> {
return this.af.auth.map(auth => {
if (auth === null) {
this.router.navigate(['/login']);
return false;
} else {
return true;
}
}).first();
}
}
EDIT: Temporarily, I changed the LoginComponent to redirect to the PageComponent if a user is logged in. I still wonder, though, why redirectTo isn't working.
I don't know exactly why this is happening, but I believe if you check the LoginGuard before the PageModule loads, it will work.
app.routing.ts
const routes: Routes = [
{ path: '', redirectTo: '/page/1', pathMatch: 'full' },
{
path: 'page',
// Call the guard before the module is loaded
canLoad: [ LoginGuard ]
loadChildren: 'app/modules/page/page.module#PageModule'
}
]
export const routing = RouterModule.forRoot(routes);
LoginGuard
#Injectable()
export class LoginGuard implements CanActivate, CanLoad {
constructor(private af: AngularFire, private router: Router) {}
// Add this method to validade the canLoad
canLoad(route: Route): Observable<boolean> {
return this.canActivate();
}
canActivate(): Observable<boolean> {
return this.af.auth.map(auth => {
if (auth === null) {
this.router.navigate(['/login']);
return false;
} else {
return true;
}
}).first();
}
}
You should NOT navigate inside a Guard
This is happening because you call this.router.navigate(['/login']); directly from your route guard which initializes a new route navigation on top of the one currently running.
You create a "race" between two navigations; my guess is the one to /login' wins because the other one has to lazy load the module (takes some time). But after the loading is done, it changes to that route afterwards, hence the "flashing" login popup.
You should NOT navigate inside a Guard, instead you should always return either a boolean (to allow navigate true/false) or a UrlTree if you want to redirect/change the route. The Guard returns the value and the router will then change navigation to the provided UrlTree for you inside the ongoing/triggered navigation and you won't get any race.
So change your method like this, and it will work correct.
canActivate(): Observable<boolean|UrlTree> {
return this.af.auth.map(auth => {
if (auth === null) {
return this.router.parseUrl('/login');
}
return true;
}).first();
}
You should see it like this, if you would call this.router.navigate in several route guards then the router wouldn't know where to navigate to, by returnin a UrlTree this problem is resolved. See also a related question/answer here

Angular 2 AuthGuard Service with redirect?

I have an application that I am building that implements CanActivate on the dashboard route. It works fine except on page reload, I check a flag in the user service to see if a user is logged in or not. By default this flag is false which kicks the user out to login. Also on page reload I am trying to fetch user data with a token in localStorage, if fetch is successful, I want them to be able to stay on the dashboard. The problem is that I am seeing a glimpse of login and having to manually redirect them to the dashboard. Is there any way to fix this to where the authGuard doesn't do anything until after it checks the API? Code is here: https://github.com/judsonmusic/tfl
dashboard:
import { Component, ViewChild } from '#angular/core';
import { LoginComponent } from "../login.component";
import { UserService } from "../user.service";
import { SimpleChartComponent } from "../charts/simpleChart.component";
import { AppleChartComponent } from "../charts/appleChart.component";
import { BarChartComponent } from "../charts/barChart.component";
import { DonutChartComponent } from "../charts/donutChart.component";
import { AlertComponent } from 'ng2-bootstrap/ng2-bootstrap';
import { ModalDemoComponent } from "../modals/modalDemoComponent";
import { NgInitHelperComponent } from "../helpers/nginit.helper.component";
import { ModalDirective } from "ng2-bootstrap/ng2-bootstrap";
import { MODAL_DIRECTIVES, BS_VIEW_PROVIDERS } from 'ng2-bootstrap/ng2-bootstrap';
#Component({
selector: 'dashboard',
templateUrl: '/app/components/dashboard/dashboard.component.html',
providers: [UserService, BS_VIEW_PROVIDERS],
directives: [SimpleChartComponent, AppleChartComponent, BarChartComponent, DonutChartComponent, AlertComponent, ModalDemoComponent, NgInitHelperComponent, ModalDirective]
})
export class DashboardComponent {
public areas: any;
constructor() {
this.areas = [
"Spiritual",
"Habits",
"Relationships",
"Emotional",
"Eating Habits",
"Relaxation",
"Exercise",
"Medical",
"Financial",
"Play",
"Work/ Life Balance",
"Home Environment",
"Intellectual Well-being",
"Self Image",
"Work Satisfaction"
]
}
}
Routes:
import { Routes, RouterModule } from '#angular/router';
import { AboutComponent } from './components/about.component';
import { PageNotFoundComponent } from "./components/pageNotFound.component";
import { HomeComponent } from "./components/home.component";
import { DashboardComponent } from "./components/dashboard/dashboard.component";
import { SurveyComponent } from "./components/survey/survey.component";
import { ResourcesComponent } from "./components/resources.component";
import { LogoutComponent } from "./components/logout.component";
import { AuthGuard } from "./components/auth-guard.service";
import { loginRoutes, authProviders } from './login.routing';
import { LoginComponent } from "./components/login.component";
const appRoutes:Routes = [
{ path: '', component: HomeComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
{ path: 'logout', component: LogoutComponent },
{ path: 'resources', component: ResourcesComponent },
{ path: 'survey', component: SurveyComponent },
{ path: 'about', component: AboutComponent },
{ path: 'login', component: LoginComponent },
{ path: '**', component: PageNotFoundComponent }
];
export const appRoutingProviders: any[] = [
authProviders
];
export const routing = RouterModule.forRoot(appRoutes);
login route:
import { Routes } from '#angular/router';
import { AuthGuard } from './components/auth-guard.service';
import { AuthService } from './components/auth.service';
import { LoginComponent } from './components/login.component';
export const loginRoutes: Routes = [
{ path: 'login', component: LoginComponent }
];
export const authProviders = [
AuthGuard,
AuthService
];
In AuthGuard do the following:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate() {
if (/*user is logged in*/) {
this.router.navigate(['/dashboard']);
return true;
} else {
this.router.navigate(['/Login']);
}
return false;
}
}
Here's how to correctly handle redirects in a guard by using an UrlTree
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivateChild {
constructor(
private authService: AuthService,
private logger: NGXLogger,
private router: Router
) {}
canActivateChild(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> {
return this.authService.isLoggedIn().pipe(
map(isLoggedIn => {
if (!isLoggedIn) {
return this.router.parseUrl('/login');
}
return true;
})
);
}
}
Big thanks to Angular In Depth for the explanation!
You can now return a UrlTree from an AuthGuard, or a boolean true / false.
Kind of amazed nobody has mentioned this yet! Sorry no example right now, but the idea is pretty simple.
I actually changed my service to this and it works:
import { Injectable } from '#angular/core';
import { CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '#angular/router';
import { AuthService } from './auth.service';
import {UserService} from "./user.service";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router, private userService: UserService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn){
console.log('ATUH GUARD SAYD THEY ARE ALREADY LOGGED IN!');
return true;
}else {
this.userService.getUser().subscribe((user) => {
console.log('AUTH GUARD GETTING USER', user);
if (user._id) {
this.authService.isLoggedIn = true;
// Store the attempted URL for redirecting
this.authService.redirectUrl = state.url;
this.router.navigate(['/dashboard']);
return true;
}else{
console.log('Validation Failed.');
localStorage.clear();
this.router.navigate(['/login']);
return false;
}
}, (error) => {
console.log('There was an error.');
this.router.navigate(['/login']);
return false
});
}
}
}
I solved it like this and used it in my AuthGuard
isLoggedIn(): Observable<boolean> {
return this.afAuth.authState
.pipe(
take(1),
map(user => {
return !!user;
},
() => {
return false;
}
),
tap(loggedIn => {
if (!loggedIn) {
this.router.navigate(['/']);
}
}
));
}
This is what I did for canActivate
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// ensure the user is properly logged in and the url he's requesting is within his right
if (this.authSvc.getRole().trim().length > 0 && this.authSvc.getToken().trim().length > 0
&& state.url.includes(this.authSvc.getRole().trim())) {
let url: string;
// base on user's role send to different url to check
if (this.authSvc.getRole().trim() === 'client') {
url = ClientAccessRequestUrl;
} else {
url = AdminAccessRequestUrl;
}
return this.http.post<AccessRequestResponse>(url, {
token: this.authSvc.getToken(),
}).pipe(map(response => {
console.log('response is:', response);
// check successful then let go
if (response.reply === true) {
return true;
// unless go to visitor site
} else {
return this.router.createUrlTree(['/visitor']);
}
}));
} else {
return this.router.createUrlTree(['/visitor']);
}
}
The best way to do redirects after authentication is structuring the logic as shown below;
in the AuthGuard,
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
{
// keep the requsted url for redirect after login
let url: string = state.url;
// call the authentication function
var authenticated = this.http.isAuthenticated();
var subject = new Subject<boolean>();
authenticated.subscribe(
(res) => {
//if not authenticated redirect to the login route with the initial route attached as an query param 'returnUrl'
if(!res.successState) {
this.router.navigate(['/login'], {queryParams: {returnUrl: url}});
}else{
// the user is authenticated just go to the requested route
subject.next(res.successState);
}
});
return subject.asObservable();
}
in the login route
loginAction(data: any){
// if the auth works fine the go the route requested before the inconviniences :)
if(data.successState){
// get the query params to see if there was a route requested earlier or they just typed in the login route directly
this.route.queryParams.subscribe(params => {
// console.log(params); //returnUrl
if(params.returnUrl){
// pearse the url to get the path and query params
let urlTree: UrlTree = this.router.parseUrl(params.returnUrl);
let thePathArray : any[] = [];
// populate it with urlTree.root.children.primary.segments[i].path;
for(let i = 0; i < urlTree.root.children.primary.segments.length; i++){
thePathArray.push(urlTree.root.children.primary.segments[i].path);
}
let the_params = urlTree.queryParams;
this.router.navigate(thePathArray, {queryParams: the_params});
}else{
this.router.navigate(['']);
}
});
}else{
// tell them about it and let them try again or reset the password
}
}
That should work perfectly. it will even preserve query params for the initial request.
THANKS

Angular 2, How to display current route name? (router 3.0.0-beta.1)

I want to display the name of the route in the app.component.html template. I'm looking for a simple solution, something that can be written like this:
{{router.currentRoute.name}}
My current router config:
export const routes: RouterConfig = [
{
path: '',
redirectTo: '/catalog',
pathMatch: 'full'
},
{
path: 'catalog',
name: 'Catalog', // Is this property deprecated?
component: CatalogComponent
},
{
path: 'summary',
name: 'Summary',
component: SummaryComponent
}
];
If your routes are configured with your route name in the data property like this:
{
path: 'somepath',
component: SomeComponent,
data: {
name: 'My Route Name'
}
}
In your app.component.ts you can import 'rxjs/add/operator/filter'; + import { ActivatedRoute, Router, NavigationEnd } from '#angular/router'; and do the following:
constructor(
private route: ActivatedRoute,
private router: Router
) { }
ngOnInit() {
this.router.events
.filter(event => event instanceof NavigationEnd)
.subscribe(event => {
let currentRoute = this.route.root;
while (currentRoute.children[0] !== undefined) {
currentRoute = currentRoute.children[0];
}
console.log(currentRoute.snapshot.data);
})
}
This will listen for NavigationEnd events and then traverse down to the current route so that you can access the data of that route.
If you are on /somepage using the code above, it should print { name="My Route Name"} in your console.
constructor(
private route: ActivatedRoute,
private router: Router
) { }
ngOnInit() {
this.router.events.pipe(
.filter(event => event instanceof NavigationEnd)).subscribe(event => {
let currentRoute = this.route.root;
while (currentRoute.children[0] !== undefined) {
currentRoute = currentRoute.children[0];
}
console.log(currentRoute.snapshot.data);
});
}```
try this if anybody facing issue with filter

Categories

Resources