I currently have a home page that routes to all the different workflows my company runs. We have about 15 different workflows and each workflow is guarded by a user role. If you don't have the correct user role in the database you wont see corresponding link to that page. We protect the server end points, but what I am worried about is what is the best way to show people the links or not show them I would prefer to not duplicate code.
Here is one way to do it:
I have a html page like this
<ul>
<li *ngIf="authService.hasRequiredRole('users.user-admin')" routerLink="/user">Users</li>
<li *ngIf="authService.hasRequiredRole('users.role-admin')" routerLink="/role">Roles</li>
</ul>
I have an auth service like this:
hasRequiredRole(roles: string | [string]) {
if (typeof roles === 'string') {
roles = [roles];
}
for (const roleSlug of roles) {
if (this.user.roles.find((role: any) => {
return role.slug === roleSlug;
})) {
return true;
}
}
return false;
}
and I have a router with routes like this:
const routes: Routes = [{
path: 'user',
data: {
allowedRoles: 'users.user-admin'
},
loadChildren: 'app/user/user.module#UserModule',
canActivate: [AuthGuard]
},
{ path: 'home', component: HomeComponent }]
The AuthGuard just checks if the user is logged in and then uses the data in the route to check if the user has the correct role.
As you can see there are two separate locations where we use the string 'users.user-admin'.
I think we should have a router with two guards. One guard will check if the user is logged in, and another to check if the user has the correct role. The role will be hard coded there like this:
export class UserAdminGuard implements CanActivate {
constructor(private router: Router, private authService: AuthService) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable < boolean > | Promise < boolean > | boolean {
return this.authService.hasRequiredRole('users.user-admin');
}
}
and then the html would look something like this:
<ul>
<li *ngIf="userAdminGuard.canActivate()" routerLink="/user">Users</li>
<li *ngIf="roleAdminGuard.canActivate()" routerLink="/role">Roles</li>
</ul>
Would something like my method work or is there a better more angular way of doing this? I could not find anything in the documentation about anything like this. If you could also provide documentation extra points.
I think one way of doing that is by creating a Structural Directive. You can create a RolesDirective which handles the logic of showing/hiding content as per the role(s) you pass it.
For example:
Your directive will look something like this
@Directive({
selector: '[roles]',
inputs: ['roles']
})
export class RolesDirective {
constructor(private _templateRef: TemplateRef<any>,
private _viewContainer: ViewContainerRef,
private userService: UserService) {
}
@Input() set roles(allowedRoles: Array<string>) {
let shouldShow: boolean = false;
let userRoles:Array<string> = this.userService.getUserRoles();
for(let role of userRoles){
if(role.toUpperCase() == "ADMIN"){
shouldShow = true;
break;
}
for(let allowedRole of allowedRoles){
allowedRole = allowedRole.toUpperCase();
if(allowedRole.toUpperCase() == role.toUpperCase()){
shouldShow = true;
break;
}
}
}
if (shouldShow) {
this._viewContainer.createEmbeddedView(this._templateRef);
} else {
this._viewContainer.clear();
}
}
}
And in your html just pass the allowed roles for that component
<ul>
<li *roles="['admin']" routerLink="/user">Users</li>
<li *roles="['other','roles']" routerLink="/role">Roles</li>
</ul>
Here's the documentation to Structural Directive: https://angular.io/guide/structural-directives
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With