Pressing the big red button - Authorisation handling with Angular
Tags: angular, typescript, tutorial, javascript
Published: 2020-09-29 18:45:20

Although you should _never_ leave [authorisation][1] handling to be dealt with only by the Front-End, customers usually require us to hide or disable UI elements based on roles and/or permissions. This makes up for better user experience and can make the developer's life a little monotone.


[1]: https://searchsoftwarequality.techtarget.com/definition/authorization "Authorization is the process of giving someone permission to do or have something. In multi-user computer systems, a system administrator defines for the system which users are allowed access to the system and what privileges of use (such as access to which file directories, hours of access, amount of allocated storage space, and so forth)."

!["Pressing the Big Red Button"](https://dev-to-uploads.s3.amazonaws.com/i/2bfolvjc1fanf40vb7hz.png "Pressing the Big Red Button")

If you'd like to jump right into the code, you can check out my [ng-reusables][2] git repository. I hope you have fun!

[2]: https://github.com/TapaiBalazs/angular-reusables

## Let's just use dependency injection

I have had the chance to work with several enterprise application Front-Ends and when it came to authorisation, usually a role-based approach got implemented. The user's roles were either provided in the JWT, which was then stored in `localStorage`, or sent back in the login response, and stored in `indexedDb`. For this blog post, it is not important how the user roles get to the Front-End, but let's state that there is an `AuthorisationService`, which handles this upon application startup.

```typescript
@Injectable({ providedIn: "root" })
export class AuthorisationService {
  private USER_ROLES: Set<string> = new Set()

  // ...

  setRoles(roles: string[]): void {
    this.USER_ROLES = new Set(roles)
  }

  hasReadAccess(role: string): boolean {
    return this.USER_ROLES.has(`${role}_READ`)
  }

  hasWriteAccess(role: string): boolean {
    return this.USER_ROLES.has(`${role}_WRITE`)
  }
}
```

We intentionally store the roles in a `Set`, because, as opposed to an array, it is more performant to check if the user has a given access right or not.
In this particular case, the application differentiates between `read` and `write` access. Read access displays the UI element, write access allows the user to interact with it. Usually, one feature has one role, let's have a feature for _pressing the big red button_. This feature would have two roles for the user: `BIG_RED_BUTTON_READ` and `BIG_RED_BUTTON_WRITE`. Let's create a component for this feature.


```html
<!-- big-red-button.component.html -->
<section *ngIf=authorisationService.hasReadAccess('BIG_RED_BUTTON')
         class="big-red-button-container">
  <button [disabled]="!authorisationService.hasWriteAccess('BIG_RED_BUTTON') || isButtonDisabled()"
          class="big-red-button">
    DO NOT PRESS
  </button>
</section>
```


```typescript
@Component({
  selector: `big-red-button`,
  templateUrl: "./big-red-button.component.html",
  styles: [
    `
      /* styles */
    `,
  ],
})
export class BigRedButtonComponent {
  constructor(public authorisationService: AuthorisationService) {}

  isButtonDisabled(): boolean {
    let isButtonDisabled = false
    // Imagine complex boolean logic here.
    return isButtonDisabled
  }
}
```


## Scaling problems

This approach works perfectly for such a small component, and let's be fair if our whole application is one big red button we can call it a day.
However, this method gets rather tedious and tiresome for a larger application. This approach is not scalable, because you have to inject the service into each and every one of your components. That means stubbing it in every component unit test, setting it up with mock data, and mocking the user rights as well. This also goes against the DRY (Don't Repeat Yourself) principle. How can we move the necessary logic into our component templates? The answer lies in structural directives.

```typescript
@Directive({ selector: "[authorisation]" })
export class AuthorisationDirective implements OnDestroy {
  private userSubscription: Subscription
  private role: string

  constructor(
    private userService: UserService,
    private authorisationService: AuthorisationService
  ) {
    this.userSubscription = this.userService.currentUser$.subscribe(
      this.updateView.bind(this)
    )
  }

  @Input()
  set authorisation(role: string) {
    this.role = role
    this.updateView()
  }

  ngOnDestroy(): void {
    this.userSubscription?.unsubscribe()
  }

  updateView(): void {
    // TODO view update logic based on access rights.
  }
}
```

This is our starting directive, which we are going to expand upon. I inject two services, the `UserService` handles the user data. When the current user changes, we need to update our views, that is why we subscribe to the user changes. Whenever a change occurs, every active directive instance
will update their view. We implement the `OnDestroy` lifecycle hook because directives use those as well. We handle the teardown logic inside it.
The `authorisation` setter gets decorated with the `@Input` decorator. This way we can use this structural directive on any HTML element in our templates as the following: `<div *authorisation="BIG_RED_BUTTON"></div>`.

With this setup, we can start implementing the view handling logic. We are going to need two important Angular template handler tools, the `ViewContainerRef` and the `TemplateRef`. Let's inject these to our constructor and implement the display/hide logic for `read` access rights and provide a solution for disabling UI elements when the user does not have `write` access right.

```typescript
interface AuthorisationContext {
  $implicit: (b: boolean) => boolean
}

@Directive({ selector: "[authorisation]" })
export class AuthorisationDirective implements OnDestroy {
  // ...
  private viewRef: EmbeddedViewRef<AuthorisationContext> = null

  constructor(
    private userService: UserService,
    private authorisationService: AuthorisationService,
    @Inject(ViewContainerRef) private viewContainer: ViewContainerRef,
    @Inject(TemplateRef) private templateRef: TemplateRef<AuthorisationContext>
  ) {
    //..
  }

  // ..

  private updateView(): void {
    const hasReadRight = this.authService.hasReadAccess(this.role)
    if (hasReadRight) {
      const hasWriteRight = this.authService.hasWriteAccess(this.role)
      this.viewContainer.clear()
      this.viewRef = this.viewContainer.createEmbeddedView(
        this.templateRef,
        this.createContext(hasWriteRight)
      )
    } else {
      this.viewContainer.clear()
      this.viewRef = null
    }
  }

  private createContext(hasWriteRight: boolean): AuthorisationContext {
    return {
      $implicit: (booleanValue: boolean) => !hasWriteRight || booleanValue,
    }
  }
}
```

First, we declare the `AuthorisationContext` interface. It has an `$implicit` property, which comes in handy when we want to use it as a template variable. We also prepare the `viewRef` member property, which stores our `EmbeddedViewRef` or null if the user does not have `read` access.
Then, we call the `clear()` method on our `ViewContainerRef` instance. When the user has read access, we call `clear()` again. This comes in handy when the `authorisation` setter gets called with a different role for which we need to update the previous view. After that, we create our `EmbeddedViewRef` using the template reference that we inject into our constructor, and we create our context. Now let's update our component, so it uses our directive.

```html
<!-- big-red-button.component.html -->
<section
  *authorisation="'BIG_RED_BUTTON'; let checkWriteAccess"
  class="big-red-button-container"
>
  <button
    [disabled]="checkWriteAccess(isButtonDisabled())"
    class="big-red-button"
  >
    DO NOT PRESS
  </button>
</section>
```

```typescript
@Component({
  selector: `big-red-button`,
  templateUrl: "./big-red-button.component.html",
  styles: [
    `
      /* styles */
    `,
  ],
})
export class BigRedButtonComponent {
  constructor() {}

  isButtonDisabled(): boolean {
    let isButtonDisabled = false
    // IMAGINE COMPLEX BOOLEAN LOGIC HERE
    return isButtonDisabled
  }
}
```

Our directive deals with the DOM, it manipulates it. This is the reason why we use the asterisk(\*) prefix. It means that this directive is a structural directive and as such, Angular internally translates the `*authorisation` attribute into an `<ng-template>` element, wrapped around the host element. Finally, our rendered `<section>` element looks like the following:

```html
<!--bindings={
  "ng-reflect-authorisation": "BIG_RED_BUTTON"
}-->
<section _ngcontent-c0 class="big-red-button-container">
  <!-- ommited -->
</section>
```

With this solution, we successfully reduced the complexity of our component, and we created a scalable and reusable solution. It is important to mention, that the directive should be declared on the application root level, and it needs to be exported. I suggest putting this into a shared
module. Also, it is important to emphasize, that this is only a Front-End solution, this does not protect your API endpoints from unauthorised access.

## What about reactive forms?

An excellent question! While the `[disabled]="checkWriteAccess(isButtonDisabled())"` works well on buttons, and on template-driven forms, it
can cause problems with reactive form inputs. Namely, binding to the `[disabled]` attribute can cause 'changed after checked' errors. Angular itself warns about this, and recommends using the `.disable()` and `.enable()` methods on form controls. Luckily, we can enhance our directive with the capability to store a `FormControl` if passed, and disable it when `updateView` is called.

```typescript
@Directive({ selector: "[authorisation]" })
export class AuthorisationDirective implements OnDestroy {
  private formControl: AbstractControl = null

  // ...

  @Input()
  set authorisationControl(ctrl: AbstractControl) {
    this.formControl = ctrl
    this.updateView()
  }

  // ...

  private updateView(): void {
    const hasReadRight = this.authService.hasReadAccess(this.role)
    if (hasReadRight) {
      const hasWriteRight = this.authService.hasWriteAccess(this.role)
      this.viewContainer.clear()
      this.viewRef = this.viewContainer.createEmbeddedView(
        this.templateRef,
        this.createContext(hasWriteRight)
      )
      if (!hasWriteRight) {
        this.formControl?.disable()
      }
    } else {
      this.viewContainer.clear()
      this.viewRef = null
    }
  }
}
```

We have added a new `@Input()` property to our directive. This allows us to pass any control that implements the `AbstractControl`, such as `FormControl`, `FormGroup` and `FormArray`. We can leverage this using the following directive binding:

```html
<!-- launch-codes.component.html -->
<form
  *authorisation="'LAUNCH_CODE_INPUTS'; control launchCodesForm"
  [formGroup]="launchCodesForm"
>
  <label for="primary-high-ranking-officer">First officer access code:</label>
  <input
    id="primary-high-ranking-officer"
    formControlName="firstOfficerAccessCode"
  />

  <label for="secondary-high-ranking-officer"
    >Second officer access code:</label
  >
  <input
    id="secondary-high-ranking-officer"
    formControlName="secondOfficerAccessCode"
  />
</form>
```

```typescript
@Component({
  selector: "launch-codes",
  templateUrl: "./launch-codes.component.html",
})
export class LaunchCodesComponent {
  readonly launchCodesForm: FormGroup = this.fb.group({
    firstOfficerAccessCode: ["", Validators.required],
    secondOfficerAccessCode: ["", Validators.required],
  })

  constructor(private fb: FormBuilder) {}
}
```

This way when the `launchCodesForm` is disabled if the user does not have write access.

## We need more fancy

So the authorisation logic works, the button gets disabled when the user does not have `write` right, however, our customer wants something extra.
The goal is to make read-only components differ from full-access components. For the sake of simplicity, in this example we are going to add some opacity to these elements, so they can still be read, but they differ visibly. Let's create the CSS class first.

```css
/* styles.css file */
.app-unauthorised {
  opacity: 0.5 !important;
}
```

Now, we could easily add `[class.app-unauthorised]="checkWriteAccess(false)` to our template, but then again, we would need to do this to every element, which has our directive on it. We don't want that, it would not be DRY... Instead, we could use a little DOM manipulation with the help of the `ElementRef`. Since we want to manipulate the DOM, we inject the `Renderer2` as well. Let's update our directive.

```typescript
@Directive({ selector: "[authorisation]" })
export class AuthorisationDirective implements OnDestroy {
  // ...

  constructor(
    private userService: UserService,
    private authorisationService: AuthorisationService,
    @Inject(ViewContainerRef) private viewContainer: ViewContainerRef,
    @Inject(TemplateRef) private templateRef: TemplateRef<AuthorisationContext>,
    @Inject(ElementRef) private el: ElementRef,
    private renderer: Renderer2,
  ) {
    //..
  }

  // ..

  private updateView(): void {
    const hasReadRight = this.authService.hasReadAccess(this.role)
    if (hasReadRight) {
      const hasWriteRight = this.authService.hasWriteAccess(this.role)
      this.viewContainer.clear()
      this.viewRef = this.viewContainer.createEmbeddedView(
        this.templateRef,
        this.createContext(hasWriteRight)
      )
      if (!hasWriteRight) {
        this.formControl?.disable()
        this.setUnauthorised()
      }
    } else {
      this.viewContainer.clear()
      this.viewRef = null
    }
  }

  // ...

  private setUnauthorised(): void {
    this.renderer.addClass(this.el.nativeElement.previousSibling, 'app-unauthorised');
  }
}
```

First, we inject the `ElementRef` into our directive. When the user has only `read` rights, the `app-unauthorised` class gets added to our `nativeElement`'s `previousSibling`. The reason for this is that this kind of directive binding gets converted into an HTML comment in the template as mentioned before. The previous sibling is the element that you apply the structural directive to. Note, that if you use structural directives, like `*ngIf`, you can see `<!---->` in _production_ built
Angular applications. This is the reason why we cannot bind more than one structural directive to an element, therefore, if we'd like to use this authorisation directive with an `*ngIf` structural directive as well, we should wrap the element inside an `<ng-container>` and apply one of the structural directives onto that container element.

## Conclusion

Authorisation handling on the UI can be a tedious job, especially when it is one of the last things to implement in an application. I hope this article has shed some light on how you can use the power of directives in your app to make your job easier.