{"id":18805,"date":"2018-03-07T21:09:00","date_gmt":"2018-03-07T21:09:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/premier_developer\/?p=18805"},"modified":"2019-03-01T10:26:27","modified_gmt":"2019-03-01T17:26:27","slug":"angular-how-to-implement-role-based-security","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/angular-how-to-implement-role-based-security\/","title":{"rendered":"Angular How-to: Implement Role-based security"},"content":{"rendered":"<p><strong><a href=\"https:\/\/www.linkedin.com\/in\/atkinsonlaurie\/\">Laurie Atkinson<\/a>, Premier Developer Consultant,<\/strong> shows us how to customize the behavior of an Angular app based on the user\u2019s permissions. This includes page navigation, hiding and disabling of UI elements, and generation of menus.<\/p>\n<hr align=\"center\" size=\"2\" width=\"100%\" \/>\n<p>Applications often include requirements to customize their appearance and behavior based on the user\u2019s role or permission. Users should only be presented with certain choices based on their role or a set of actions they have permission to perform. This is not a replacement for securing the data at the API level, but improves the usability on the client. This post provides sample code that you can use to implement this feature in your Angular app.<\/p>\n<h2>Create an authorization service<\/h2>\n<p>Centralize the checking of permissions into an Angular service.<\/p>\n<p><strong>authorization.service.ts<\/strong><\/p>\n<pre class=\"lang:default decode:true\">import { Injectable } from '@angular\/core';\r\nimport { AuthGroup } from '..\/models\/authorization.types';\r\nimport { AuthorizationDataService } from '.\/authorization-data.service';\r\n\r\n@Injectable()\r\nexport class AuthorizationService {\r\n\r\n    permissions: Array&lt;string&gt;; \/\/ Store the actions for which this user has permission\r\n\r\n    constructor(private authorizationDataService: AuthorizationDataService) { }\r\n\r\n    hasPermission(authGroup: AuthGroup) {\r\n        if (this.permissions &amp;&amp; this.permissions.find(permission =&gt; {\r\n                return permission === authGroup;\r\n            })) {\r\n            return true;\r\n        }\r\n        return false;\r\n    }\r\n\r\n    \/\/ This method is called once and a list of permissions is stored in the permissions property\r\n    initializePermissions() {\r\n        return new Promise((resolve, reject) =&gt; {\r\n            \/\/ Call API to retrieve the list of actions this user is permitted to perform. (Details not provided here.)\r\n            \/\/ In this case, the method returns a Promise, but it could have been implemented as an Observable\r\n            this.authorizationDataService.getPermissions()\r\n                .then(permissions =&gt; {\r\n                    this.permissions = permissions;\r\n                    resolve();\r\n                })\r\n                .catch((e) =&gt; {\r\n                    reject(e);\r\n                });\r\n        });\r\n    }\r\n}\r\n<\/pre>\n<p><code><\/code><\/p>\n<p><strong>authorization.types.ts<\/strong><\/p>\n<pre class=\"lang:default decode:true\">export type AuthGroup = 'VIEW_ONLY' | 'UPDATE_FULL' | 'CREATE';<\/pre>\n<p><code><\/code><\/p>\n<h2>Create attribute directives to hide and disable elements<\/h2>\n<p>To hide or disable an element based on permission, use the following code to create two directives. This will enable the Angular templates to use this syntax:<\/p>\n<pre class=\"lang:default decode:true\">&lt;div [myHideIfUnauthorized]=\"updatePermission\"&gt; &lt;!-- a property set or passed into the component --&gt;<\/pre>\n<p><strong>disable-if-unauthorized.directive.ts<\/strong><\/p>\n<pre class=\"lang:default decode:true\">import { Directive, ElementRef, OnInit, Input } from '@angular\/core';\r\nimport { AuthorizationService } from '..\/..\/services\/authorization.service';\r\nimport { AuthGroup } from '..\/models\/authorization.types';\r\n\r\n@Directive({\r\n    selector: '[myDisableIfUnauthorized]'\r\n})\r\nexport class MyDisableIfUnauthorizedDirective implements OnInit {\r\n    @Input('myDisableIfUnauthorized') permission: AuthGroup; \/\/ Required permission passed in\r\n\r\n    constructor(private el: ElementRef, private authorizationService: AuthorizationService) { }\r\n\r\n    ngOnInit() {\r\n        if (!this.authorizationService.hasPermission(this.permission)) {\r\n            this.el.nativeElement.disabled = true;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p><code><\/code><\/p>\n<p><code><\/code><\/p>\n<p><strong>hide-if-unauthorized.directive.ts<\/strong><\/p>\n<pre class=\"lang:default decode:true \">import { Directive, ElementRef, OnInit , Input } from '@angular\/core';\r\nimport { AuthorizationService } from '..\/..\/services\/authorization.service';\r\nimport { AuthGroup } from '..\/models\/authorization.types';\r\n\r\n@Directive({\r\n    selector: '[myHideIfUnauthorized]'\r\n})\r\nexport class MyHideIfUnauthorizedDirective implements OnInit {\r\n    @Input('myHideIfUnauthorized') permission: AuthGroup; \/\/ Required permission passed in\r\n\r\n    constructor(private el: ElementRef, private authorizationService: AuthorizationService) { }\r\n\r\n    ngOnInit() {\r\n        if (!this.authorizationService.hasPermission(this.permission)) {\r\n            this.el.nativeElement.style.display = 'none';\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<h2>Create a CanActivate guard to prevent unauthorized routing<\/h2>\n<p>Angular includes a feature to prevent navigation to a page by implementing a CanActivate guard and specifying it in the route configuration. Unfortunately, there is no option to pass a parameter into the guard service, but a work-around is to use the data property of the route. When using this CanActivate guard in the route table, the programmer must also provide the route data value. This example uses a value named auth.<\/p>\n<p><strong>auth-guard.service.ts<\/strong><\/p>\n<pre class=\"lang:default decode:true\">import { Injectable } from '@angular\/core';\r\nimport { CanActivate, Router, ActivatedRouteSnapshot } from '@angular\/router';\r\nimport { AuthorizationService } from '.\/authorization.service';\r\nimport { AuthGroup } from '..\/models\/authorization.types';\r\n\r\n@Injectable()\r\nexport class AuthGuardService implements CanActivate {\r\n\r\n    constructor(protected router: Router,\r\n                protected authorizationService: AuthorizationService) { }\r\n\r\n    canActivate(route: ActivatedRouteSnapshot): Promise&lt;boolean&gt; | boolean {\r\n        return this.hasRequiredPermission(route.data['auth']);\r\n    }\r\n\r\n    protected hasRequiredPermission(authGroup: AuthGroup): Promise&lt;boolean&gt; | boolean {\r\n        \/\/ If user\u2019s permissions already retrieved from the API\r\n        if (this.authorizationService.permissions) {\r\n            if (authGroup) {\r\n                return this.authorizationService.hasPermission(authGroup);\r\n            } else {\r\n                return this.authorizationService.hasPermission(null);\r\n            }\r\n        } else {\r\n            \/\/ Otherwise, must request permissions from the API first\r\n            const promise = new Promise&lt;boolean&gt;((resolve, reject) =&gt; {\r\n                this.authorizationService.initializePermissions()\r\n                    .then(() =&gt; {\r\n                        if (authGroup) {\r\n                            resolve(this.authorizationService.hasPermission(authGroup));\r\n                       } else {\r\n                            resolve(this.authorizationService.hasPermission(null));\r\n                        }\r\n                    }).catch(() =&gt; {\r\n                        resolve(false);\r\n                    });\r\n            });\r\n            return promise;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p><code><\/code><\/p>\n<p><code><\/code><\/p>\n<p>Include the canActivate property of the route definition together with the data property in order to pass in the required permission.<\/p>\n<p><strong>routing.module.ts<\/strong><\/p>\n<pre class=\"lang:default decode:true \">const routes: Routes = [\r\n    {\r\n        path: 'feature',\r\n        canActivate: [AuthGuardService], \/\/ Could nest parent auth requirements as well as child\r\n        children: [\r\n            {\r\n                path: '',\r\n                children: [\r\n                    {\r\n                        path: 'searchresults',\r\n                        component: SearchResultsComponent,\r\n                        canActivate: [AuthGuardService],\r\n                        data: { auth: 'VIEW_ONLY' },\r\n                        resolve: { searchResults: SearchResultsResolver }\r\n                    },\r\n<\/pre>\n<h2>Call the authorization service elsewhere in the app<\/h2>\n<p>In addition to the attribute directives and the CanActivate guard for routing, the authorization service can be called throughout the app. For instance, a menu service could use the permission checking method to hide menu items if the user does not have the required permission.<\/p>\n<p><strong>menu.service.ts<\/strong><\/p>\n<pre class=\"lang:default decode:true \">  private showMenuItem(authGroup: AuthGroup) {\r\n      return this.authorizationService.hasPermission(authGroup);\r\n  }\r\n<\/pre>\n<p>Remember, this is all merely JavaScript and a determined and savvy user could still work around these safeguards, but the goal is to improve the experience for the user. It is still the job of the serverside code to secure the data behind the API.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Laurie Atkinson, Premier Developer Consultant, shows us how to customize the behavior of an Angular app based on the user\u2019s permissions. This includes page navigation, hiding and disabling of UI elements, and generation of menus. Applications often include requirements to customize their appearance and behavior based on the user\u2019s role or permission. Users should only [&hellip;]<\/p>\n","protected":false},"author":582,"featured_media":37840,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[122],"tags":[51,40,52,58],"class_list":["post-18805","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","tag-angular","tag-development","tag-laurie-atkinson","tag-security"],"acf":[],"blog_post_summary":"<p>Laurie Atkinson, Premier Developer Consultant, shows us how to customize the behavior of an Angular app based on the user\u2019s permissions. This includes page navigation, hiding and disabling of UI elements, and generation of menus. Applications often include requirements to customize their appearance and behavior based on the user\u2019s role or permission. Users should only [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/18805","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=18805"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/18805\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/37840"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=18805"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=18805"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=18805"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}