Mocking a client authentication service

Perhaps the word mocking, which is pretty common in the context of unit testing, is a bit misleading here but at least serves as a heads-up for what we are going to build now. In the previous section, we implemented a pretty simple user authentication checking but, in a real scenario, we usually delegate all the heavy lifting on an authentication service that wraps all the necessary tools for handling user login, logout, and sometimes authentication for granting access to protected areas of our application.

Next, we will create a simplified version of such service and will put it in charge of handling user login along with the component we just created in the previous section. This service will also manage auth token persistence and provide methods to check if the user has access granted to secure pages.

Before jumping into the code, let's summarize the minimum requirements this service must fulfil:

  • We need its API to expose a method to handle user login
  • User logout must be handled as well by a public method in this API
  • A third method or property should inform if the user is logged in or not so they can proceed to secured pages
  • Having an observable property informing of the current state of the active user for authentication will become handy to make the overall UI more reactive

With these specifications in mind, let's build our ideal authentication service. Since this service is component-agnostic and will have an impact on the whole application, we will store it in the services folder of our shared context, applying the naming conventions we already know and exposing it through the shared facade:

app/shared/services/authentication.service.ts

import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export default class AuthenticationService {

    constructor() {}

    login({username, password}): Promise<boolean> {}

    logout(): Promise<boolean> {}

    static isAuthorized(): boolean {}
}

app/shared/shared.ts

import Queueable from './interfaces/queueable';
import Task from './interfaces/task';

import FormattedTimePipe from './pipes/formatted-time.pipe';
import QueuedOnlyPipe from './pipes/queued-only.pipe';

import AuthenticationService from './services/authentication.service';
import SettingsService from './services/settings.service';
import TaskService from './services/task.service';

const SHARED_PIPES: any[] = [
  FormattedTimePipe,
  QueuedOnlyPipe
];

const SHARED_PROVIDERS: any[] = [
  AuthenticationService,
  SettingsService,
  TaskService
];

export {
  Queueable,
  Task,

  FormattedTimePipe,
  QueuedOnlyPipe,
  SHARED_PIPES,

  AuthenticationService,
  SettingsService,
  TaskService,
  SHARED_PROVIDERS
};

As you can see in the resulting facade, the new service will become part of the SHARED_PROVIDERS group token. Then, it will be available for our application injector, since this symbol is being declared in the providers array of our root component.

Back to the service class, we imported the Injectable decorator. As you know, we will need it if we want our AuthService class to be automatically instantiated and injected as a singleton in our components by Angular (in case our class requires its own dependencies in the future). We also import the EventEmitter class, which we will cover later in this section.

In the body of the AuthenticationService class, we have defined an empty constructor and three methods with no implementation (one of them being static). While the names give a very good hint of the purpose of each method, perhaps the last one requires some more elaboration: The isAuthorized() method will inform if the user has permissions to access secured pages. The reason why it is static is because we will need to use it in some areas where Angular's dependency injection machinery cannot reach so no automatic provider injection is available.

Our first requirement was to provide a public method to handle user login. Let's go for it. Get back to the AuthenticationService module and extend the login method with the following implementation:

app/shared/services/authentication.service.ts

login({username, password}): Promise<boolean> {
  return new Promise(resolve => {
    let validCredentials: boolean = false;

    // @NOTE: In a real scenario this check 
    // should be performed against a web service:
    if (username === '[email protected]' && 
        password === 'letmein') {
          validCredentials = true;
          window.sessionStorage.setItem('token', 'eyJhbGciOi');
    }

    resolve(validCredentials);
  });
}

As you can see from the comments inline in the code, we are not submitting data for validation to a remote web service although we should definitely do. Please recall the warning we raised in previous chapters: you should never implement user validation this way. Having said that, let's review this implementation. In the first place, there is something that draws our attention: the returning type. This method is supposed to return a Promise and there is a good reason for that. Usually, you would also want to implement an async HTTP connection to a remote service so you can send the user credentials and wait for a response. Hence, we use the asynchronous interface in the form of a returning Promise, which resolves to a Boolean value informing if the credentials provided are good to access the system or not. On the other hand, the method signature is not an annotated argument, but a deconstructed object informing that this method will expect any type or object in its payload containing both username and password properties. Last but not least, right after conducting our fake user validation, we store a random token onto the user's browser using the browser's own session storage layer. This is a common way of handling authentication and user session persistence nowadays, with the sole difference that the token is usually sent in the body of the server response and thereafter is sent back in the request headers on every information request made to the server.

Conducting a server-side implementation is beyond the scope of this book, so we will not explore that topic in greater depth. You can refer to the Packt library for further reference.

Now that we know how to handle user login, implementing a user logout() method that literally reverses what the previous login() method did is pretty easy:

app/shared/services/authentication.service.ts

logout(): Promise<boolean> {
  return new Promise(resolve => {
    window.sessionStorage.removeItem('token');
   resolve(true);
  });
}

Our third requirement was to provide a property or method that would tell us if the user is authenticated, so they can proceed to secured pages. Keeping in mind that user permission is tied to the existence or absence of a security token stored in the browser storage layer, the method logic is really simple:

static isAuthorized(): boolean {
  return !!window.sessionStorage.getItem('token');
}

We will discuss this method and the rationale behind its static annotation later on. Now, let's move into the last bit of our service: providing an Observable that allows UI elements and other application clients to subscribe to updates in the user status. First, we will create a public EventEmitter member, which we can use to send notifications every time the user logs in and out so that other classes and components can subscribe to it as mere observers and react to those events. Obviously, the login and logout methods will be updated to also send the corresponding notifications to the observers depending on the actions taken and the user state at all times.

With all these changes, this is the final layout of our injectable authentication service:

import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export default class AuthenticationService {
  userIsloggedIn: EventEmitter<boolean>;

  constructor() {
    this.userIsloggedIn = new EventEmitter();
  }

  login({ username, password }): Promise<boolean> {
    return new Promise(resolve => {
      let validCredentials: boolean = false;

      // @NOTE: In a normal scenario this check
      // should be performed against a web service:
      if (username === '[email protected]' &&
        password === 'letmein') {
        validCredentials = true;
        window.sessionStorage.setItem('token', 'eyJhbGciOi');
      }

      this.userIsloggedIn.emit(validCredentials);
      resolve(validCredentials);
    });
  }

  logout(): Promise<boolean> {
    return new Promise(resolve => {
      window.sessionStorage.removeItem('token');
      this.userIsloggedIn.emit(false);
      resolve(true);
    });
  }

  static isAuthorized(): boolean {
    return !!window.sessionStorage.getItem('token');
  }
}

Exposing our new service to other components

With the authentication service provider now available from our application injector, we can begin hooking it up in other components: the login feature is the most logical starting point. First, open the LoginComponent code unit and import the new AuthenticationService token so that we can properly use its type to inject it into the component:

app/login/login.component.ts

import { Component } from '@angular/core';
import { 
  FormBuilder,
  ControlGroup,
  Validators,
  Control } from '@angular/common';
import { Router } from '@angular/router-deprecated';
import { AuthenticationService } from '../shared/shared';
...

In the same code unit, let's now update the constructor payload with a new argument annotated with the AuthenticationService token, so the Angular 2 DI machinery becomes aware that this module requires that type to be injected:

constructor(
  formBuilder: FormBuilder,
  private router: Router,
  private authService: AuthService) { 
  // Rest of constructor implementation remains unchanged
   ...
}

With all the code in place, now we can replace our authenticate() method to remove the business logic:

authenticate() {
  let credentials: any = this.loginForm.value;
  this.notValidCredentials = !this.loginForm.valid && 
                              this.loginForm.dirty;

  this.authenticationService.login(credentials).then(success => {
    if (success) {
      this.router.navigateByUrl('/');
    } else {
      this.notValidCredentials = true;
    }
  });
}

Blocking unauthorized access

With all this in place, it's time to actually prevent unlogged users from accessing protected content. In our case, it just entails protecting the task editing form component from unauthorized requests. In the previous chapter, we saw how to allow or prevent component instantiation by means of Router hooks.

With that knowledge to hand, protecting the task editor component from undesired visits becomes quite simple. Open the TaskEditorComponent file, import our new AuthenticationService provider, and check whether the user is authorized by binding the execution of the static isAuthorized() method to the CanActivate decorator:

app/tasks/task-editor.component.ts

...
// Other import statements remain as they are already
import {
  Task,
  TaskService,
  AuthenticationService } from '../shared/shared';

@Component({
  selector: 'pomodoro-tasks-editor',
  directives: [ROUTER_DIRECTIVES],
  providers: [Title],
  templateUrl: 'app/tasks/task-editor.component.html',
  styles: [`
        .ng-valid { border-color: #3c763d; }
        .ng-invalid { border-color: #a94442; }
        .ng-untouched { border-color: #999999; }
    `]
})
@CanActivate(AuthService.isAuthorized)
export default class TaskEditorComponent implements OnActivate, CanDeactivate, OnDeactivate {
  // The class implementation remains the same
  ...
}

And that's it! Now, any unlogged user attempting to access the protected task editor component will get nothing! If you look carefully at the @CanActivate() decorator, you will understand why we defined the isAuthorized() method of the AuthenticationService as static. The reason relies on the fact that we can only inject dependency singletons in our components but not in decorators. While this is not exactly true (we can leverage the provide() injector to bring the desired singleton although the code required is nowhere near as simple or neat), the truth is that this implementation is simple: providing the same level of effectiveness in a neat and clear fashion.

Note

The ideal scenario would be to inject both the AuthenticationService and the Router providers in the CanActivate implementation, and then redirect the user to the login page should the user is not logged in.

Unfortunately, at the time of wrapping up the writing of this book, there is still no formal support for dependency injection in the context of the CanActivate router hook. However, this issue is part of the features that will become part of Angular 2 Final. It is quite likely that the @CanActivate decorator will be replaced by an analogue instance method of a parent routing component once Angular 2 becomes final eventually. Please refer to the official documentation.

Making the UI reactive to the user authentication status

All right, so unauthorized users cannot access the task editor form component. However, having an unresponsive link in our main toolbar is definitely not good, so we should leverage the Observable features of the AuthenticationService to flip the UI whenever there is a change in the user login status.

Right now, the nav bar features the Login link that leads the user login form page. What we want to do is to hide the Publish Task link and make sure we only display it when the user is logged in, no matter where and how this login procedure was undertaken. On the other hand, we also want to offer the end user a Logout link when logged in, so they can shut down his session in confidence. This logout link should be made available for logged in users only. Access to the protected component by hardcoding URLs is not a concern, since the @CanActivate decorator will do its job to keep undesired users away.

Now that we have described the requirements, let's put them into practice. Open the top root component file and update its implementation (it remained empty until now). We will need the AuthenticationService and the Router dependencies to do so, so make sure to import them at the top of the file:

app/app.component.ts

import { Component } from '@angular/core';
import { 
  SHARED_PROVIDERS,
  AuthenticationService } from './shared/shared';
import { HTTP_PROVIDERS } from '@angular/http';
import {
  ROUTER_PROVIDERS,
  RouteConfig,
  ROUTER_DIRECTIVES,
  Router } from '@angular/router-deprecated';
// Rest of import statements remain the same
…

With the tokens properly declared in the import statements, we can move on and provide an implementation for the AppComponent class:

app/app.component.ts

...
export default class AppComponent {
  userIsLoggedIn: boolean;

  constructor(
    private authenticationService: AuthenticationService,
    private router: Router) {
    authenticationService.userIsloggedIn.subscribe(isLoggedIn => {
      this.userIsLoggedIn = isLoggedIn;
    });
  }

  logout($event): void {
    $event.preventDefault();

    this.authenticationService.logout().then(success => {
      if (success) {
        this.router.navigateByUrl('/');
      }
    });
  }
}

We have declared a userIsLoggedIn Boolean field, which will change its value every time the observable userIsloggedIn member of the injected AuthenticationService type changes its value. We also injected the Router type and created a new component method named logout() that will wipe out the user session and redirect the user to the root page upon signing out from the application.

This gives us the chance to wrap up the application UI by updating the root component template to make the sensible links fully reactive to these changes:

app/app.component.html

<nav class="navbar navbar-default navbar-static-top">
  <div class="container">
    <div class="navbar-header">
      <strong class="navbar-brand">My Pomodoro App</strong>
    </div>
    <ul class="nav navbar-nav navbar-right">
      <li><a [routerLink]="['TasksComponent']">Tasks</a></li>
      <li><a [routerLink]="['TimerComponent']">Timer</a></li>
      <li *ngIf="userIsLoggedIn">
        <a [routerLink]="['TaskEditorComponent']">Publish Task</a>
      </li>
      <li *ngIf="!userIsLoggedIn"><a [routerLink]="['LoginComponent']">Login</a>
      </li>
      <li *ngIf="userIsLoggedIn">
        <a href="#" (click)="logout($event)">Logout</a>
      </li>
    </ul>
  </div>
</nav>
<router-outlet></router-outlet>

Give it a try! Reload the application, check the links available at the nav bar, head over to the login page, proceed to login with the credentials, and check the nav bar again... Magic!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset