Secure Angular4 with Keycloak Role or Group

Demo template is available for sale for $50. You can send payment via skype at: czetsuya@gmail.com

This article will be an extension of an existing one: http://czetsuya-tech.blogspot.com/2017/10/how-to-secure-angular-app-using.html.

Normally in the front-end side (Angular), we are only concern whether a user is authenticated or not. Authorization whether a user has enough access to a given REST resource is configured in the api side. But what if we want to secure the front-end urls nonetheless? One of the many solution is to create a guard and either add an authorization code in canLoad or canActivate. In this article we will deal with canLoad, as we are lazy-loading our modules (please refer to Angular documentation for more information).

In this particular example, we are checking for the group claim tied to a user. Role would also do, but I normally used that on the REST api side.

Here are the steps:

  1. Create a permission-guard model that we will use when defining the route
    export interface PermissionGuard {
    Only?: Array string>,
    Except?: Array string>,
    RedirectTo?: string | Function
    }
  2. Add the route in your app-routing.module.ts file, and define the Permission model in the data attribute.
    {
    path: 'dashboard/employee',
    canLoad: [AuthGuard],
    loadChildren: 'app/module/dashboard/employee/employee.module#EmployeeModule',
    data: {
    Permission: {
    Only: ['employee'],
    RedirectTo: '403'
    } as PermissionGuard
    }
    }
  3. In our keycloak.service, add the following methods. It checks if a user is a member of a group specified in the PermissionGuard.
    static hasGroup( groupName: string ): boolean {
    return KeycloakService.auth.authz != null &&
    KeycloakService.auth.authz.authenticated
    && KeycloakService.auth.authz.idTokenParsed.groups.indexOf( "/" + groupName ) !== -1 ? true : false;
    }

    static hasGroups( groupNames: Array ): boolean {
    return groupNames.some( v => {
    if ( typeof v === "string" )
    return KeycloakService.hasGroup( v );
    } );
    }
  4. Going back to the auth-guard.service, let's modify the canLoad method to process the authorization. In here we use the previously defined method to check if the current logged user is a member of an specific group.
    canLoad( route: Route ): boolean {
    if ( KeycloakService.auth.loggedIn && KeycloakService.auth.authz.authenticated ) {
    this.logger.info( "user has been successfully authenticated" );

    } else {
    KeycloakService.login();
    return false;
    }

    this.logger.info( "checking authorization" );

    let data = route.data["Permission"] as PermissionGuard;

    if ( Array.isArray( data.Only ) && Array.isArray( data.Except ) ) {
    throw "Can't use both 'Only' and 'Except' in route data.";
    }

    if ( Array.isArray( data.Only ) ) {
    let hasDefined = KeycloakService.hasGroups( data.Only )
    console.log("hasDefined="+hasDefined);
    if ( hasDefined )
    return true;

    if ( data.RedirectTo && data.RedirectTo !== undefined )
    this.router.navigate( [data.RedirectTo] );

    return false;
    }

    if ( Array.isArray( data.Except ) ) {
    let hasDefined = KeycloakService.hasGroups( data.Except )
    if ( !hasDefined )
    return true;

    if ( data.RedirectTo && data.RedirectTo !== undefined )
    this.router.navigate( [data.RedirectTo] );

    return false;
    }
    }
  5. An additional bonus is having a directive that we can use in the UI side to hide / show an element when a user is either a member of a group or not
    import { Directive, OnInit, ElementRef, Input } from '@angular/core';

    import { KeycloakService } from 'app/core/auth/keycloak.service';

    @Directive( {
    selector: '[hasGroup]'
    } )
    export class HasGroupDirective implements OnInit {

    @Input( 'hasGroup' ) group: string;
    @Input( 'hasGroups' ) groups: string;

    constructor( private element: ElementRef, ) { }

    ngOnInit() {
    if ( KeycloakService.hasGroup( this.group ) ) {
    this.element.nativeElement.style.display = '';
    } else {
    this.element.nativeElement.style.display = 'none';
    }
    }
    }


Questions:
  1. What is 403? It's an additional route to redirect when a user is not authorized.
    { path: '403', component: ForbiddenComponent },

Note: This article is inspired by ng2-permission.

Demo template is available for sale for $50. You can send payment via skype at: czetsuya@gmail.com

0 nhận xét:

Đăng nhận xét