简体   繁体   中英

Angular JWT with few Roles acess

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.com/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.com/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-compact/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

It could also be that a typescript enum is not a string. so comparing a enum with a string will never be true.

What you need to use is a const enum because that compiles 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.org/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 comprehensive tutorial on how to set up your Authentication and Authorization in this guide: https://jasonwatmore.com/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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM