Skip to content

Usage with routes

AlexKhymenko edited this page Jul 26, 2021 · 4 revisions
  1. Introduction

  2. Property only and except

  3. Single permission/role

  4. Multiple permissions/roles

  5. Dynamic access

  6. Property redirectTo

  7. Single rule redirection

  8. Multiple rule redirection

  9. Dynamic redirection rules

  10. Default redirection in configuration service

  11. Implemented Guards

  12. Can Activate Guard

  13. Can Load Guard

  14. Can Activate Child Guard

  15. Common use cases

  16. Two guards when first make request for authorisation and gets permissions second checks for permissions

Introduction

Now you are ready to start working with controlling access to the states of your application. In order to restrict any state ngx-permission rely on angular-route's data property, reserving key permissions allowing to define authorization configuration.

Permissions object accepts following properties:

Property Accepted value
only [String|Array|Function]
except [String|Array|Function]
redirectTo [String]

Property only and except

Property only:

  • is used to explicitly define permission or role that are allowed to access the state
  • when used as String contains single permission or role
  • when used as Array contains set of permissions and/or roles

Property except:

  • is used to explicitly define permission or role that are denied to access the state
  • when used as String contains single permission or role
  • when used as Array contains set of permissions and/or roles

🔥 Important
If you combine both only and except properties you have to make sure they are not excluding each other, because denied roles/permissions would not allow access the state for users even if allowed ones would pass them.

Single permission/role

In simplest cases you allow users having single role permission to access the state. To achieve that you can pass as String desired role/permission to only/except property: You can use except and only at the same time;

import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
import { HomeComponent } from './home/home.component';
import { NgxPermissionsGuard } from 'ngx-permissions';

const appRoutes: Routes = [
  { path: 'home',
    component: HomeComponent,
    canActivate: [NgxPermissionsGuard],
    data: {
      permissions: {
        only: 'ADMIN'
      }
    }
  },
];
@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

In given case when user is trying to access home state NgxPermissionsGuard service is called checking if isAuthorized permission is valid:

  • if permission definition is not found it stops transition

Multiple permissions/roles

Often several permissions/roles are sufficient to allow/deny user to access the state. Then array value comes in handy:

import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
import { HomeComponent } from './home/home.component';
import { NgxPermissionsGuard } from 'ngx-permissions';

const appRoutes: Routes = [
  { path: 'home',
    component: HomeComponent,
    canActivate: [NgxPermissionsGuard],
    data: {
      permissions: {
        only: ['ADMIN', 'MODERATOR'],
        except: ['GUEST']
      }
    }
  },
];
@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

When NgxPermissionsGuard service will be called it would expect user to have either ADMIN or MODERATOR permissions to pass him to home route.

Dynamic access

You can find states that would require to verify access dynamically - often depending on parameters.

Let's imagine situation where user want to modify the invoice. We need to check every time if he is allowed to do that on state level. We are gonna use ActivatedRouteSnapshot and RouterStateSnapshot object to check weather he is able to do that.

To make AOT compatible you should export function. Below is presented code AOT Compatible

AOT compatible

export function testPermissions(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
  if (route.params['id'] === 42) {
    return ['MANAGER', "UTILS"]
  } else {
    return 'ADMIN'
  }
}
const appRoutes: Routes = [
  { path: 'dynamic/:id',
      component: HomeComponent,
      canActivate: [NgxPermissionsGuard],
      data: {
        permissions: {
          only: testPermissions
        }
      }
    }
];

💀 Warning
Below is presented code not AOT compatible

const appRoutes: Routes = [
  { path: 'dynamic/:id',
      component: HomeComponent,
      canActivate: [NgxPermissionsGuard],
      data: {
        permissions: {
          only: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
              if (route.params['id'] === 42) {
                  return ['MANAGER', "UTILS"]
                } else {
                  return 'ADMIN'
                }
          }
        }
      }
    }
];

So whenever we try access state with param id = 42 set to true additional check for permission manager and utils will be made. Otherwise only ADMIN will be required.

🔥 Important
Notice that function require to always return array or string of roles/permissions in order to work properly.

Property redirectTo

Property redirectTo:

  • when used as String defines single redirection rule
  • when used as Objects defines single/multiple redirection rules
  • when used as Function defines dynamic redirection rule(s)

Single redirection rule

In case you want to redirect to some specific state when user is not authorized pass to redirectTo path of that route.

import { RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
import { HomeComponent } from './home/home.component';
import { NgxPermissionsGuard } from 'ngx-permissions';

const appRoutes: Routes = [
  { path: 'home',
    component: HomeComponent,
    canActivate: [NgxPermissionsGuard],
    data: {
      permissions: {
        only: ['ADMIN', 'MODERATOR'],
        redirectTo: '/another-route'
      }
    }
  },
];
@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

In order to pass additional properties like params use pass redirectTo as object. navigationCommands and navigationExtras are reserved words it corresponds to parameters passed to router.navigate function navigate(commands: any[], extras: NavigationExtras): Promise<boolean>

const appRoutes: Routes = [
  { path: 'home',
    component: HomeComponent,
    canActivate: [NgxPermissionsGuard],
    data: {
      permissions: {
        only: ['ADMIN', 'MODERATOR'],
        redirectTo: {
            navigationCommands: ['123'],
            navigationExtras: {
                skipLocationChange: true
            }
        }               
    }
  },
];
@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  exports: [
    RouterModule
  ]
})

Multiple redirection rules

In some situation you want to redirect user based on denied permission/role to create redirection strategies. In order to do that you have to create redirection Object that contain keys representing rejected permissions or roles and values implementing redirection rules.

Redirection rules are represented by following values:

Value type Return Usage
String [String] Simple state transitions
Object [Object] Redirection with custom parameters or options
Function [String|Object] Dynamic properties-based redirection

💡 Note
Use default property that will handle fallback redirect for not defined permissions.

The simplest example of multiple redirection rules are redirection based on pairs role/permission and state. When user is not granted to access the state will be redirected to agendaList if missing canReadAgenda permission or to dashboard when missing canEditAgenda. Property default is reserved for cases when you want handle specific cases leaving default redirection.

  const appRoutes: Routes = [
    { path: 'home',
      component: HomeComponent,
      canActivate: [NgxPermissionsGuard],
      data: {
       permissions: {
               only: ['canReadAgenda','canEditAgenda'],
               redirectTo: {
                 canReadAgenda: 'agendaList',
                 canEditAgenda: 'dashboard',
                 default: 'login'
               }
        }
      }
    },
  ];
  @NgModule({
    imports: [
      RouterModule.forRoot(appRoutes)
    ],
    exports: [
      RouterModule
    ]
  })

If you need more control over redirection parameters Object as a value can be used to customise target url navigationCommands and transition navigationExtras.

💡 Note navigationCommands and navigationExtras are reserved words it corresponds to parameters passed to router.navigate function navigate(commands: any[], extras: NavigationExtras): Promise<boolean>

  const appRoutes: Routes = [
    { path: 'home',
      component: HomeComponent,
      canActivate: [NgxPermissionsGuard],
      data: {
         permissions: {
               only: ['canEditAgenda'],
               redirectTo: 
                 canEditAgenda: {
                   navigationCommands: 'dashboard',
                   navigationExtras: {
                       skipLocationChange: true
                   }
                 
                 },
                 default: 'login'
             }
          }
      }
    },
  ];
  @NgModule({
    imports: [
      RouterModule.forRoot(appRoutes)
    ],
    exports: [
      RouterModule
    ]
  })  

To present usage redirectTo as Object with values as Function in a state definition agenda presented below redirection rules are interpreted as:

  • when user does not have canReadAgenda invoked function returns string representing the state name to which unauthorized user will be redirected
  • when user does not have canEditAgenda invoked function returns object with custom options and params that will be passed along to transited dashboard url
 const appRoutes: Routes = [
    { path: 'home',
      component: HomeComponent,
      canActivate: [NgxPermissionsGuard],
      data: {
       permissions: {
              only: ['canReadAgenda','canEditAgenda'],
              redirectTo: {
                canReadAgenda: (rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routeStateSnapshot: RouterStateSnapshot) => {
                  return 'dashboard';
                },
                canEditAgenda: (rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routeStateSnapshot: RouterStateSnapshot) => {
                  return {
                      navigationCommands: ['/dashboard'],
                      navigationExtras: {
                          skipLocationChange: true
                      }
                  }
                },
                default: 'login'
            }
        }
      }
    },
  ];
  @NgModule({
    imports: [
      RouterModule.forRoot(appRoutes)
    ],
    exports: [
      RouterModule
    ]
  })

🔥 Important
Above code is not AOT compatible to make it AOT compatible extract it to function navigationCommands and navigationExtras reserved words. Matching parameter to router.navigate function

export function canReadAgenda(rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routeStateSnapshot: RouterStateSnapshot) => {                                                 
    return 'dashboard';
},

redirectTo: {
    canReadAgenda: canReadAgenda
 
}

Dynamic redirection rules

Similarly to examples showing defining dynamic access to state redirection can also be defined based on any parameters of ActivatedRouteSnapshot and RouterStateSnapshot;

💡 Note
Remember to always return from function state name or object.

const appRoutes: Routes = [
    { path: 'home/:isEditable',
      component: HomeComponent,
      canActivate: [NgxPermissionsGuard],
      data: {
      permissions: {
             only: ['canReadAgenda','canEditAgenda'],
             redirectTo: (rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot) => {
               if(activateRouteSnapshot.params['id'] === 42){
                 return 'login';
               } else {
                 return 'dashboard'
               }
             }
      }
    },
  ];
  @NgModule({
    imports: [
      RouterModule.forRoot(appRoutes)
    ],
    exports: [
      RouterModule
    ]
  })

🔥 Important
Above code is not AOT compatible to make it AOT compatible extract it to function

export function redirectToFunc(rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot) => {
     if(activateRouteSnapshot.params['id'] === 42){
       return 'login';
     } else {
       return 'dashboard'
     }
   }

redirectTo: redirectToFunc

default-redirection-configuration-service

Default redirection configuration service

Also its possible to set up default redirection in configuration service

          this.ngxConfigurationService.defaultRedirectTo = 'login'

or as a function

        function loginRedirect(activateRouteSnapshot: ActivatedRouteSnapshot,
                               routerStateSnapshot: RouterStateSnapshot) {
            return 'login';
        }

      this.ngxConfigurationService.defaultRedirectTo = loginRedirect

Implemented Guards

Can Activate Guard

NgxPermissionsGuard implements CanActivate interface for examples you can see above

Can Load Guard

NgxPermissionsGuard implements CanLoad Interface. Functionality is the same as with canActivate

const appRoutes: Routes = [

  {
    path: 'lazy',
    data: {
      permissions: {
        except: 'ADDDMIN',
      }
    },
    canLoad: [NgxPermissionsGuard],
    loadChildren: 'app/lazy-module/lazy-module.module#LazyModule'
  },
 

];
@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  exports: [
    RouterModule
  ],
  providers: [
    // CanDeactivateGuard
  ]
})
export class AppRoutingModule {}


 

🔥 Warning

  • The only difference if you use as a function the parameter is only 1 and its type of Route
{
    path: 'lazy',
    data: {
      permissions: {
         only: (route: Route) => {
                  //logic here
                   return ['MANAGER', "UTILS"]
                  }
      }
    },
    canLoad: [NgxPermissionsGuard],
    loadChildren: 'app/lazy-module/lazy-module.module#LazyModule'
  },

Can Activate Child Guard

NgxPermissionsGuard implements CanLoad Interface. Functionality is the same as with canActivate

🔥 Warning

  • Need to remember that rules and data you should specify on Child Components not on parent component
const appRoutes: Routes = [
  { path: '',
    component: IsolateComponent,
    canActivateChild: [NgxPermissionsGuard],
    children: [
      {
        path: 'except-should',
        component: AnotherComponent,
        data: {
          permissions: {
            except: 'ADMIN'
          }
        }
      },
      {
        path: 'only-should',
        component: ComeComponent,
        data: {
          permissions: {
            only: 'GUEST'
          }
        }
      },
    ]
  },
];