I have an angular app.
When a user logs in, I want to display different UI based on user's role.
A user can be just a customer or an admin
In my routing module I have the following
{path: '', redirectTo: 'admin-portal' , pathMatch: 'full'},
{path: 'admin-portal', component: AdminPortalComponent, canActivate: [AuthGuard]},
{path: 'customer-portal', component: CustomerPortalComponent, canActivate: [AuthGuard]}
I want to be able to fetch a variable from local storage and use it to decide where I redirect the user when the app loads. I am thinking of having a condition like {path: '', redirectTo: 1===1 ? 'admin-portal' : 'customer-portal' , pathMatch: 'full'}
You create another guard and put the conditions inside
#Injectable()
export class LoadGuard implements CanActivate {
constructor(private router: Router) {
}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if(1===1){
this.router.navigate(['admin-portal']);
return false;
}else{
this.router.navigate(['customer-portal']);
return false;
}
}
}
and then in the route
{path: '', pathMatch: 'full',component: sampleComponent, canActivate: [LoadGuard]}
Related
Say I have a routing module with the following setup:
const routes: Routes = [
{path: 'login', component: LoginComponent},
{path: '', component: MainComponent,
children: [
{ path: '', redirectTo: '/home', pathMatch: 'full'},
{ path: 'dashboard/:id', component: DashboardComponent },
{path: '404', component: HomeComponent},
{path: '**', redirectTo: '/404'}
]
}];
Is it possible to restrict access to the following:
https://website.com/dashboard/1 (where only users who fall into 1 can see 1)
https://website.com/dashboard/2 (where only users who fall into 2 can see 2)
If a 1 goes to dashboard/2 they would be routed to an error page or vice versa. Is this possible?
That's the job of the Guards. You can implement canActivate function and pass to it preferable argument for the identification.
class UserToken {}
class Permissions {
canActivate(user: UserToken, id: string): boolean {
return true;
}
}
#Injectable()
class CanActivateTeam implements CanActivate {
constructor(private permissions: Permissions, private currentUser: UserToken) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree {
return this.permissions.canActivate(this.currentUser, route.params.id);
}
}
and after just configure your route with the guard:
{ path: 'dashboard/:id', component: DashboardComponent, canActivate:['Your Guard Name'] },
On successful authentication user will be able to continue. On fail you will implement redirect logic with angular Router.
My Anuglar 4 app has multiple routes, in below example there are only two of them - Logging and List of items. So basically there are two routes: http://localhost:4200/#/ and http://localhost:4200/#/items. When I am at the http://localhost:4200/#/items and reload the page it automatically navigates me to http://localhost:4200/#/ which from my point of view is wrong behaviour. Is there a nice way how to prevent it? And when currently I am at the http://localhost:4200/#/items and reload the page to stay at the same page?
Below I post kind of configuration which might help you:
<base href="/">
imports: [
RouterModule.forRoot([
{path: '', component: LoginComponent},
{path: 'items', component: ItemListComponent, canActivate: [AuthGuard]},
])
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptor, multi: true},
{provide: LocationStrategy, useClass: HashLocationStrategy},
AuthenticationService,
AuthGuard,
],
AuthGuard:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthenticationService) {
}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isLoggedIn;
}
}
#Injectable()
export class AuthenticationService extends DataService{
private _isLoggedIn: boolean;
constructor(http: HttpClient) {
super(http);
this._isLoggedIn = false;
this.url = '/api/auth';
}
get isLoggedIn(): boolean{
return this._isLoggedIn;
}
set isLoggedIn(value: boolean){
this._isLoggedIn = value;
}
And the server returns the token. For your information after reloading there is still valid token keeping in localStorage
This has nothing to do with authtoken, but all about your canActivate. When your app is initialized, means that app is destroyed and recreated. So when on a route that requires the logged in status, you have initially declared _isloggedIn as false. So the guard is working properly and redirecting you to your LoginComponent.
You would want to use localStorage or something else to persist your logged in status in case of page refresh.
add pathMatch: 'full'
{path: '', component: LoginComponent, pathMatch: 'full'},
Angular is a Single Page Application based framework. Page refresh will be done only when the app is initialized. You can make use of two-way binding, make a call to back-end and update the list of items received in response.
You can also try router.navigate(['/items']), it will work only when you are navigating from other components.
I have define 2 Routes LoginComponent and DashboardComponent and their is one main component which is the base component appComponent (which i have not used)
What I want is:
First Load the LoginComponent and then I Login Successfully then I want to Load the DashboardComponent.
What I have done is:
I have make the base component to the loginComponent as a selector in index.html page and when I successfully login the the LoginComponent View not Removed and Replace to Dashboard Page.
Here is my Code:
Routes:
const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'dashboard', component: DashboardComponent },
{ path: '', redirectTo: '/login',pathMatch:'full' },
{ path: '**', component: NotFound404Component }
];
Used this for login function:
this.router.navigate(['/dashboard']);
Any help would appriciated.
I am trying to figure out why the router.navigate(['']) code below does not take the user to the login component. I just stay on the home component. When I added debugger; to the code it seems like it goes into some sort of endless loop.
Here is the process:
User comes to site and instantly gets redirected to /login since the Route Guard fails. If they login, Route Guard passes and they go to [' '] which is the HomeComponent. Then when they click logout I figure the navigate to [' '] should fail and simply go to /login. For some reason it stays on the HomeComponent and never navigates.
home.component.ts
logout() {
this.userService.logout();
this.router.navigate(['']);
}
user.service.ts
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.verifyLogin(url);
}
verifyLogin(url: string): boolean {
if (this.userLoggedIn) { return true; }
this.router.navigate(['/login']);
return false;
}
logout() {
this.userLoggedIn = false;
}
app-routing.module.ts
const routes: Routes = [
{ path: '' , component: HomeComponent, canActivate: [UserService]},
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ path: '**' , redirectTo: '' }
];
For those using Angular 5+, you CAN get router.navigate(['']) to reload the same URL. It's not well documented, and coming from a server heavy framework like .NET, this feels more natural.
Added runGuardsAndResolvers: 'always' to one of your paths and onSameUrlNavigation: 'reload' to the RouterModule initialization.
app-routing.module.ts
const routes: Routes = [
{ path: '' , component: HomeComponent, canActivate: [UserService], runGuardsAndResolvers: 'always' },
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ path: '**' , redirectTo: '' }
];
#NgModule({
imports: [RouterModule.forRoot(routes, {
onSameUrlNavigation: 'reload'
})],
exports: [RouterModule]
})
export class AppRoutingModule { }
Why it should navigate it is already in [' '] and you ask again go to [' ']? You can call 'this.router.navigate(['/login'])' in logout.
logout() {
this.userLoggedIn = false;
this.router.navigate(['/login']);
}
In this exaple project with JWT authentication we se how to allow only authenticated users to some route:
import { RouterConfig } from '#angular/router';
import { Home } from './home';
import { Login } from './login';
import { Signup } from './signup';
import { AuthGuard } from './common/auth.guard';
export const routes: RouterConfig = [
{ path: '', component: Login },
{ path: 'login', component: Login },
{ path: 'signup', component: Signup },
{ path: 'home', component: Home, canActivate: [AuthGuard] },
{ path: '**', component: Login },
];
I would like make step further and also indicate what user role have 'access' to route - but I don't know how to pass argument to canActivate AuthGuard (src). So I would like to achieve something like this (for instance I have two roles: Admin and Employee):
{ path: 'home', component: Home, canActivate: [AuthGuard] },
{ path: 'users', component: AdminUsers, canActivate: [AuthGuard('Admin')] },
{ path: 'users', component: Employees, canActivate: [AuthGuard('Employee')] },
Where my AuthGuard could look something like this (where userRole(= Admin or Employee or null) is passed parameter to AuthGuard):
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(userRole) {
if (!userRole || JWT.user().role == userRole) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
where JWT.user.role is helper which read user role stored in JWT token. Is there a way to do something similar like above idea?
You can set the data parameter of the route with the role like this
const appRoutes: Routes = [
{
path: 'account/super-secure',
component: SuperSecureComponent,
canActivate: [RoleGuard],
data: { roles: ['super-admin', 'admin'] }
}];
and then have this in canActivate of RoleGuard:
canActivate(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
let roles = route.data["roles"] as Array<string>;
return (roles == null || roles.indexOf("the-logged-user-role") != -1);
}
I think this could be another way of doing it instead of creating guard for every role. I would actually take this rout since it requires less code and handles the problem very nicely.
The signature for CanActivate won't allow you to pass a userRole like you want to. https://github.com/angular/angular/blob/2.0.0-rc.4/modules/%40angular/router/src/interfaces.ts#L54
It's probably best to do separate classes for each of your user role cases. That's the guidance in the official docs too: https://angular.io/docs/ts/latest/api/router/index/CanActivate-interface.html
NOTE: applicable for angular-rc.4 <
#KamilKiełczewski, #NikolayRusev,
added in route additional data with array of routes:
...
{
path: "customers",
component: CustomersCmp,
data: { roles: ["admin"] }
},
...
and in CanActivate you can get path from first parameter, search the same path in route config and get your described roles from data:
public canActivate(route: ActivatedRouteSnapshot): boolean {
let path = route._urlSegment.pathsWithParams[0].path;
let roles;
if (route._routeConfig.path == path) {
roles = route._routeConfig.data.roles
} else {
roles = route._routeConfig.children.find(_route => _route.path == path).data.roles;
}
if (...) {
return true;
}
return false;
}
Of course it would be better to avoid private properties, and you can, but I cannot remember how I exactly done it.
But for my porposes I redid it in different way. The huge disadvantage of this approach, I mean for role based guard, is that every user with different roles can see all of the routes if you render them in a component automatically not manually.
you can extend router-outlet
http://www.captaincodeman.com/2016/03/31/angular2-route-security/
https://medium.com/#blacksonic86/authentication-in-angular-2-958052c64492#.cxgoan9go