0
votes

Authenticated users in my app can have several roles, therefore, I want to guard routes based on users' specific roles. I included roles checking in method checkAuthentication() in AuthGuardService, then apply data: { roles: ['USER'] } in routing module. I also set identity in store to detect roles for routing guard in AuthService. However, when I tried to log in, it does not route to dashboard and just stay in log in page, even though I can see tokens set in local storage.

routing-module:

  {
    path: 'home', component: HomeComponent, canActivate: [AuthGuardService], data: { roles: ['PRIMARY, SUBSTITUTE'] },
    children: [
      { path: '', redirectTo: 'offers', pathMatch: 'full' },
      { path: 'offers', component: OffersComponent, data: { roles: ['PRIMARY, SUBSTITUTE'] }, canActivateChild: [AuthGuardService] },
      { path: 'users', component: UsersComponent, data: { roles: ['PRIMARY'] }, canActivateChild: [AuthGuardService] },
      { path: 'settings', component: SettingsComponent, data: { roles: ['PRIMARY, SUBSTITUTE, USER'] },
        canActivateChild: [AuthGuardService] }
    ]
  }

auth-guard-service:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    const url: string = state.url;
    return this.checkAuthentication(url, state, route.data['roles']);
  }

  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.canActivate(route, state);
  }

  checkAuthentication(url: string, state, requiredRoles: string[]) {
    return this.authService.roles$.pipe(
      flatMap((roles: string[]) => {
        for (const role of requiredRoles) {
          if (roles.includes(role) && this.authService.hasValidToken()) {
            return of(true);
          } else if (roles.includes(role) && this.authService.hasValidToken() === false) {
            return this.authService.refreshToken().pipe(
              map((tokens: AuthToken) => {
                if (tokens.authToken) {
                  // refresh token successful
                  return true;
                }
                // refresh token did not succeed
                this.authService.logout();
                this.router.navigate(['']);
                return false;
              },
                error => {
                  this.authService.logout();
                  this.router.navigate(['']);
                  return of(false);
                })
            );
          }
        }
        this.router.navigate(['']);
        return of(false);
      })
    );
  }

auth-service:

private roles = new BehaviorSubject([]);
  public roles$ = this.roles.asObservable();

  private identity = new BehaviorSubject(<Identity>{});
  public identity$ = this.identity.asObservable();

  constructor(
    private store: Store<AppState>
  ) {
    this.store.select('tokens').subscribe((tokens: AuthToken) => {
      this.tokens = tokens;
    });

    this.store.select('identity').subscribe((identity: any) => {
      console.log(identity);
      if (identity.identity && identity.identity.roles) {
        this.setIdentity(identity.identity);
        this.setRoles(identity.identity.roles);
      } else {
        this.setRoles([]);
      }
    });
  }

  setRoles(roles: any[]) {
    this.roles.next(roles);
    console.log(roles);
  }

  setIdentity(identity: Identity) {
    this.identity.next(identity);
    console.log(identity);
  }
2

2 Answers

1
votes

See this example in stackblitz

In your app-routing.module.ts

const routes: Routes = [
   {
       path: "admin",
       component: AdminOnlyComponent,
       canActivate: [RoleGuardService],
       data: { roles: ['admin']}
   },
   ...
}

In your RoleGuardService

import { Injectable } from '@angular/core';
import { UserRolesService} from './user-roles.service';
import { Router, ActivatedRouteSnapshot } from '@angular/router';
@Injectable({
  providedIn: 'root'
})
export class RoleGuardService {

  constructor(private getUserRoles: UserRolesService) { }

  canActivate(route: ActivatedRouteSnapshot): boolean {
    return route.data.roles.some( ai => this.getUserRoles.getRoles().includes(ai) );
  }
}

In UserRolesService

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class UserRolesService {
   userRoles: string[] = [];

  constructor() { }

  setRoles(Roles: string[]){
    this.userRoles = Roles.slice(0);
  }

  getRoles(){
    return this.userRoles;
  }
}

Set roles when user logged in to the system or get those roles from your localstorage....

Hope this will helps..

0
votes

I had the same issue. You may try the followings

  1. Copy the token and check here https://jwt.io/ and verify that the token includes the roles you are expecting (case sensitive).

  2. And also, when you have multiple roles, it appears to in an array but when it is single role it is a string.