10

I want to create an authentication decorator in my application.

Usage example should be simple as

@RequireAuthentication()
@HostListener('click', ['$event']) onClick(event: Event) {
  // ...
}

As I know decorator can only be function, so in some other file I plan to have

export function RequireAuthentication() {
    if (!userService.isAuthenticated) {
        navigationService.goToLogin();
        return;
    }
}

The problem for me is how to properly initialize userService and navigationService in this case, since these services contain all logic for finding if a user is authenticated and showing login screen.

I already tried:

  1. to use a class with the constructor for service initialization, but then the nested method cannot be used as a decorator
  2. to use Injectable class to create services, I need to create an instance of this class, same problem.
  3. to use ModuleWithProviders approach to hide authentication implementation and only expose decorator, but not sure if that is the right way to do this.

Any hints would be helpful. Could be that I miss something fundamental since I'm not an experienced angular developer or there is another way to approach this problem.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Minja
  • 1,222
  • 1
  • 19
  • 28
  • I've provided some explanation on the subject. I'm still not sure what problem RequireAuthentication is supposed to solve. Are you trying to deny a click on some condition (no auth)? – Estus Flask Feb 20 '18 at 15:21
  • @estus Thanks for answer, I will take a look tomorrow. It is intended to check if user is authenticated -> no -> show login screen and prevent further method execution. – Minja Feb 20 '18 at 16:24
  • It should happen on user click action @estus – Minja Feb 21 '18 at 08:24
  • I don't think it's a good idea to use a decorator for that. It's cumbersome and can result in weird situations, because the order of decorators matters. If you reuse this functionality often (auth check + click handler), there may be better ways to handle this - a directive or base component. – Estus Flask Feb 21 '18 at 08:50
  • I was able to reuse your example and add in-between listener which will check authentication and call actual listener function in component as callback if authentication pass. Then I can only use one decorator @RequireAuthentication('click'). However, solution is quite awkward and there is one additional problem. Developer must be aware to inject 'Injectable' inside component everytime he wants to use decorator. Idea of having Authenticate decorator as in some backend frameworks sounds good, but seems not possible to support with current decorator concept in angular. Thanks for help – Minja Feb 21 '18 at 09:55

1 Answers1

8

As explained in this answer, the solution that is idiomatic to the framework is to expose injector class instance property, so it could be accessed inside decorator. The existence of injector property can be also secured with an interface.

Since property decorator runs once and has access to class prototype but not instance, it's necessary to patch ngOnOnit method and retrieve all necessary services inside patched method with this.injector.get().

The alternative is to expose global injector to some object, as explained here. This is not idiomatic solution but a hack that will result in certain limitations and negative consequences. It can hardly be recommended for use in production.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565