Search code examples
angularsecurityangular-guards

How to prevent actions by user role in Angular


I already have an AuthService to authenticate user on login and AuthGuard to prevent access if not logged in.

Some pages I restrict access by UserProfile/Role but now I need to prevent actions on page.

I have roles like "Admin, Manager, Support and Agent", from greater to lower.

How to set level only to Manager or Above to edit content on a page that all can access (Support and Agent restrict to view only)?

This is my current canActivate method:

canActivate(route: ActivatedRouteSnapshot) {

  const currentUser = JSON.parse(localStorage.getItem('currentUser'));
  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
  this.router.navigate(['auth/login']);
  return false;
}

And this is my module routing.module.ts

const routes: Routes = [{
  path: '',
  component: ReportsComponent,
  canActivateChild: [AuthGuard],
  children: [
    {
      path: 'blocked-users',
      component: BlockedUsersComponent,
      data: { roles: [Role.admin, Role.manager, Role.suporte, Role.agent] },
      children: [
        { ... 

Need to fix these two topics:

1) Line data: { roles: [] } I want to pass only the lower level (like Agent);

2) Inside component tell that only Manager can edit data (or just disable a button if Role == Support or Agent)


Solution

  • Break out your authorisation code into a service that both your guard and other services can make use of (DRY!). From there, the logic is the same for checking roles around the entire site.

    @Injectable()
    export class RoleAuthorisationService {
      public isAuthorised(roles: Role[]): boolean {
        const currentUser = JSON.parse(localStorage.getItem('currentUser'));
        if (!currentUser) return false;
        return roles.indexOf(currentUser.role) >= 0)
      }
    }
    

    The guard will be making use of it, but then you can just prevent usage of any part of your pages by making your own check against that authorisation service.

    Your guard (very approximated because it's 23:32 and I want to go to bed):

    export class YourGuard {
    
      constructor(private readonly auth: RoleAuthorisationService) {}
    
      canActivate(route: ActivatedRouteSnapshot) {
        if (route.data.roles && !this.auth.isAuthorised(route.data.roles)) {
          // role not authorised so redirect to home page
          this.router.navigate(['/']);
          return false;
        } else {
          // authorised so return true
          return true;
        }
    
        // not logged in so redirect to login page
        this.router.navigate(['auth/login']);
        return false;
      }
    }
    

    Want to hide an 'Edit' button on the screen?

    Template:

    <h1>Some details<h1>
    <button *ngIf="canEdit" (click)="enableEditing()>Edit</button>
    <p>Details etc etc...</P>
    

    Ts:

    export class SomeComponent {
      private readonly editors: Role[] = [Role.Agent, Role.Administrator];
    
      constructor(private readonly auth: RoleAuthorisationService) {}
    
      public get canEdit(): boolean { return this.auth.isAuthorised(this.editors); }
    }
    

    Edit: To add an answer for your more specific question of locking things unless a user is 'a manager or higher', you can either (as above) be very specific about the exact roles that can access the functionality, or you can use enums. So long as your enums are specified in order, you can do basic > = < checks.

    export enum Role {
      Admin = 1,
      Manager = 2,
      Support = 3,
      Peon = 4
    }
    
    ...
    
    return CurrentUser.Role <= Role.Manager;