I want to use several roles for accessing views in the application, if I use one role everything works correctly, however when I use several roles, the views do not give access
My model User have this:
export class User {
role: Role[]; // I change - role: Role[] for few roles
expiresIn: string;
aud: string;
iss: string;
token?: string;
}
export const enum Role {
Admin = 'admin',
User = 'user',
Engineer = 'engineer'
}
my backend give my token with with roles:
//....
role: (2) ["admin", "engineer"]
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
//....
If I use this in login metod
tokenInfo[''][0] - first element in array
i have only 1 role, and code work fine, but I can have many users who belong to different roles, and I need the application to give them access if there are at least 1 role
I handle token decoding and getting roles in authorization service
signin(username:string, password:string ) {
return this.http.post<User>(`${environment.apiUrl}${environment.apiVersion}Profile/Login`, {username, password})
.pipe(map(user => {
if (user && user.token) {
let tokenInfo = this.getDecodedAccessToken(user.token); // decode token
this.session = {
token: user.token,
role: tokenInfo[''], - add this [0]
expiresIn: tokenInfo.exp,
aud: tokenInfo.aud,
iss: tokenInfo.iss,
}
localStorage.setItem('currentUser', JSON.stringify(this.session));
this.currentUserSubject.next(this.session);
}
return this.session;
}))
}
sigin metod for example
Login() {
this.auth.signin(this.signinForm.value.email, this.signinForm.value.password)
.pipe(first())
.subscribe(
data => {
console.log("User is logged in");
this.router.navigate(['/dashboard']);
this.loading = false;
});
}
Not sure if I correctly specify multiple access roles
//......
const adminRoutes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./views/dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGaurd],
},
{
path: 'books',
loadChildren: () => import('./views/books/books.module').then(m => m.BooksModule),
canActivate: [AuthGaurd],
data: { roles: [Role.Admin] } <- work fine if 1 role
},
{
path: 'person',
loadChildren: () => import('./views/person/person.module').then(m => m.PersonModule),
canActivate: [AuthGaurd],
data: { roles: [Role.Admin, Role.Engineer] } <- if have 1 role - admin - open
},
{
path: 'eqip',
loadChildren: () => import('./views/eqip/eqip.module').then(m => m.PersonModule),
canActivate: [AuthGaurd],
data: { roles: [Role.Engineer] } <- not open becouse only admin role
}];
const routes: Routes = [
{
path: '',
redirectTo: 'applayout-sidebar-pact/dashboard/v1',
pathMatch: 'full',
},
...
{
path: '**',
redirectTo: 'others/404'
}];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
export class AppRoutingModule { }
//......
and guard sevice
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
const currentUser = this.auth.currentUserValue;
// in auth.service.ts
// constructor(private http: HttpClient) {
// this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
// this.currentUser = this.currentUserSubject.asObservable();
// }
// public get currentUserValue(): User {
// return this.currentUserSubject.value;
// }
if (this.auth.isUserLoggedIn()) {
// test code
const ter = route.data.roles.includes(currentUser.role) <- Error now here
console.log(ter)
// main check role code
// if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
// this.router.navigate(["/"]);
// return false;
// }
return true;
}
this.auth.setRedirectUrl(url);
this.router.navigate([this.auth.getLoginUrl()]);
return false;
}
token in localStorage:
aud: "Service"
expiresIn: 1591967261
iss: "USs"
role: ["admin", "engineer"]
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHR....
change app-routing.module.ts
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
initialNavigation: 'enabled',
paramsInheritanceStrategy: 'always',
relativeLinkResolution: 'corrected',
scrollPositionRestoration: 'enabled',
})],
exports: [RouterModule]
Error
Uncaught (in promise): TypeError: Cannot read property 'includes' of undefined
TypeError: Cannot read property 'includes' of undefined
I want to use several roles for accessing views in the application, if I use one role everything works correctly, however when I use several roles, the views do not give access
My model User have this:
export class User {
role: Role[]; // I change - role: Role[] for few roles
expiresIn: string;
aud: string;
iss: string;
token?: string;
}
export const enum Role {
Admin = 'admin',
User = 'user',
Engineer = 'engineer'
}
my backend give my token with with roles:
//....
role: (2) ["admin", "engineer"]
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
//....
If I use this in login metod
tokenInfo['http://schemas.microsoft./ws/2008/06/identity/claims/role'][0] - first element in array
i have only 1 role, and code work fine, but I can have many users who belong to different roles, and I need the application to give them access if there are at least 1 role
I handle token decoding and getting roles in authorization service
signin(username:string, password:string ) {
return this.http.post<User>(`${environment.apiUrl}${environment.apiVersion}Profile/Login`, {username, password})
.pipe(map(user => {
if (user && user.token) {
let tokenInfo = this.getDecodedAccessToken(user.token); // decode token
this.session = {
token: user.token,
role: tokenInfo['http://schemas.microsoft./ws/2008/06/identity/claims/role'], - add this [0]
expiresIn: tokenInfo.exp,
aud: tokenInfo.aud,
iss: tokenInfo.iss,
}
localStorage.setItem('currentUser', JSON.stringify(this.session));
this.currentUserSubject.next(this.session);
}
return this.session;
}))
}
sigin metod for example
Login() {
this.auth.signin(this.signinForm.value.email, this.signinForm.value.password)
.pipe(first())
.subscribe(
data => {
console.log("User is logged in");
this.router.navigate(['/dashboard']);
this.loading = false;
});
}
Not sure if I correctly specify multiple access roles
//......
const adminRoutes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./views/dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [AuthGaurd],
},
{
path: 'books',
loadChildren: () => import('./views/books/books.module').then(m => m.BooksModule),
canActivate: [AuthGaurd],
data: { roles: [Role.Admin] } <- work fine if 1 role
},
{
path: 'person',
loadChildren: () => import('./views/person/person.module').then(m => m.PersonModule),
canActivate: [AuthGaurd],
data: { roles: [Role.Admin, Role.Engineer] } <- if have 1 role - admin - open
},
{
path: 'eqip',
loadChildren: () => import('./views/eqip/eqip.module').then(m => m.PersonModule),
canActivate: [AuthGaurd],
data: { roles: [Role.Engineer] } <- not open becouse only admin role
}];
const routes: Routes = [
{
path: '',
redirectTo: 'applayout-sidebar-pact/dashboard/v1',
pathMatch: 'full',
},
...
{
path: '**',
redirectTo: 'others/404'
}];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
export class AppRoutingModule { }
//......
and guard sevice
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
const currentUser = this.auth.currentUserValue;
// in auth.service.ts
// constructor(private http: HttpClient) {
// this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
// this.currentUser = this.currentUserSubject.asObservable();
// }
// public get currentUserValue(): User {
// return this.currentUserSubject.value;
// }
if (this.auth.isUserLoggedIn()) {
// test code
const ter = route.data.roles.includes(currentUser.role) <- Error now here
console.log(ter)
// main check role code
// if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
// this.router.navigate(["/"]);
// return false;
// }
return true;
}
this.auth.setRedirectUrl(url);
this.router.navigate([this.auth.getLoginUrl()]);
return false;
}
token in localStorage:
aud: "Service"
expiresIn: 1591967261
iss: "USs"
role: ["admin", "engineer"]
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHR....
change app-routing.module.ts
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
initialNavigation: 'enabled',
paramsInheritanceStrategy: 'always',
relativeLinkResolution: 'corrected',
scrollPositionRestoration: 'enabled',
})],
exports: [RouterModule]
Error
Uncaught (in promise): TypeError: Cannot read property 'includes' of undefined
TypeError: Cannot read property 'includes' of undefined
Share Improve this question edited Jun 2, 2020 at 13:04 Ярослав Прохоров asked May 26, 2020 at 12:18 Ярослав ПрохоровЯрослав Прохоров 5311 gold badge6 silver badges28 bronze badges 6- Can you also provide your AuthGuard source code? – Berk Kurkcuoglu Commented May 28, 2020 at 16:51
-
1
Please add your
AuthGuard
code. Also, what'stokenInfo
look like? – benbotto Commented May 28, 2020 at 16:52 - add guard.service for check – Ярослав Прохоров Commented Jun 2, 2020 at 13:09
- Is you error occuring when navigating to dashboard specifically? – David Commented Jun 3, 2020 at 13:02
- 1 Dashboard route doesn't have data.roles. – cerkiner Commented Jun 4, 2020 at 10:43
3 Answers
Reset to default 4It could also be that a typescript enum is not a string.
so paring a enum
with a string will never be true.
What you need to use is a const enum
because that piles down to a string.
try changing to
export const enum Role {
Admin = 'admin',
User = 'user',
Engineer = 'engineer'
}
Though this does have other implications. https://www.typescriptlang/docs/handbook/enums.html#const-enums
and instead of doing a indexOf you can use .includes
route.data.roles.includes(currentUser.role)
Edit: It could also be that your data is not inherited down to where you are trying to get it.
You might need to add this to your Router config
RouterModule.forRoot([], {
initialNavigation: 'enabled',
paramsInheritanceStrategy: 'always', <-- this makes params and data accessible lower down into the tree
relativeLinkResolution: 'corrected',
scrollPositionRestoration: 'enabled',
}),
This really depends on how you are handling your AuthGuard code. There is a prehensive tutorial on how to set up your Authentication and Authorization in this guide: https://jasonwatmore./post/2018/11/22/angular-7-role-based-authorization-tutorial-with-example
Big area where you could be experiencing the issue is on your AuthGuard
. You can have this example from the link I shared above:
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthenticationService } from '@/_services';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthenticationService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currentUser = this.authenticationService.currentUserValue;
if (currentUser) {
// check if route is restricted by role
if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
// role not authorised so redirect to home page
this.router.navigate(['/']);
return false;
}
// authorised so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}
You also need to make sure you are passing the right roles into your AuthGuard
.
If you want deeper restrictions in the future, there's also this guide: How to prevent actions by user role in Angular
Hope this helps!
In your route config, there are some routes which don't need to check roles property in data. Assuming everybody should have access to them.
Change your auth guard to :-
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
const currentUser = this.auth.currentUserValue;
console.log(currentUser);
if (this.auth.isUserLoggedIn()) {
if (!route.data.roles || route.data.roles.length === 0) {
return true;
}
if (typeof currentUser.role === 'string' && route.data.roles.includes(currentUser.role)) {
return true;
}
if (Array.isArray(currentUser.role)) {
for (let i = 0; i < currentUser.role.length; i++) {
if (route.data.roles.includes(currentUser.role[i])) {
return true;
}
}
}
this.router.navigate([this.auth.getLoginUrl()]); //TODO: Change to 403 PAGE (403 forbidden)
return false;
}
this.auth.setRedirectUrl(url);
this.router.navigate([this.auth.getLoginUrl()]);
return false;
}