Using Observables in anger – of, pipe, and map

We now have a REST endpoint that can verify our JWT token. What we need to do, then, is to call this endpoint from our Angular application to ensure that the user that is logged in has a valid JWT token. Remember that when we log in, we store this token in Local Storage, and redirect to our SecureComponent. The SecureComponent is protected by our AuthGuard, which checks for the existence of this token. We therefore need to contact our Express server, which generated this token, in order to verify it.

The most obvious place to put this code is in the AuthGuard itself, so that any attempt to access the SecureComponent will need to verify the JWT token by POSTing to our Express server before continuing. Again, we should not be accessing REST endpoints from our code directly, so we will need a service that can contact the server on our behalf. As we already have a UserService for this purpose, we can simply create another function in this service to do the actual call to the Express server, as follows:

validateUser(token: Object): Observable<Object> { 
    const headers = new HttpHeaders(); 
    headers.append('Content-Type', 'application/json'); 
    const payload = { 
        token: token 
    }; 
    return this.httpClient.post(
'/validate-user', payload,
{ 'headers': headers }); }

Here, we have created a function named validateUser that takes a single parameter named token of type Object, and returns an Observable of type Object. Within this function, we are simply constructing a POST request to the validate-user endpoint, with the token as the content. This is simple enough, and mirrors what we did for the login endpoint.

Our changes to the AuthGuard are where things start to get a little tricky. We will update our canActivate function as follows:

canActivate(route: ActivatedRouteSnapshot,  
state: RouterStateSnapshot): 
   Observable<boolean>  
{ 
    let token = <Object>localStorage.getItem('token'); 
    console.log(`Authguard : token : ${token}`); 
    return this.userService.validateUser(token).pipe( 
        map( (e: Object) : boolean => { 
              console.log(`Authguard : e ; ${JSON.stringify(e)}`) 
              return true; 
            } ), 
        catchError((err) => { 
            // 401 unauth errors will be caught here. 
            this.router.navigate(['/login']); 
            return of(false); 
        }) 
    ); 
} 

Here, there are a number of modifications to the canActivate function. Firstly, we are not simply returning a boolean value, but we are returning an Observable of boolean. Secondly, we have updated this function to call the validateUser function of the UserService in order to POST to the REST endpoint and validate our token. The third modification to note is that we are using the pipe method instead of the subscribe method. We will discuss the pipe method in detail a little later. Within this pipe method, we have a call to a map method, as well as a catchError method. Again, we will discuss these methods in detail next.

In terms of logical flow, then, what we are doing within this function is as follows:

  1. Retrieving the JWT token from Local Storage
  2. Calling the validateUser function on the UserService and passing in the token
  3. This will issue a POST to our endpoint, and either return a valid response, or an error
  4. If a valid response is returned, return true so that the AuthGuard succeeds
  5. If an error occurs, navigate to the /login URL, and return false so that the AuthGuard fails

The syntax of this function looks pretty hairy, and can get pretty confusing, so let's break it down into smaller pieces, and discuss each piece individually.

Our canActivate function now returns a type of Observable<boolean>, instead of a plain old boolean. Let's see how we can convert a boolean value into an Observable of boolean.

To wrap a boolean value, or any other value for that matter, within an Observable, we can use the of function. As an example of this, consider the following code:

function usingObservableOf(value: number): Observable<boolean> { 
    if (value > 10) { 
        return of(true); 
    } else { 
        return of(false); 
    } 
} 

Here, we have defined a function named usingObservableOf that takes a number as a single parameter. It is returning the Observable<boolean> type, which means that it must return an Observable. The body of the function checks to see whether the value argument passed in is greater than 10. If it is, we return of(true), which, in effect, turns our Boolean true value into an Observable<boolean>. We can see the use of this of function in our canActivate function, within the catchError section.

The body of our canActivate function is retrieving the token that is stored in Local Storage, and then calling the validateUser function of our UserService. The problem that we have, however, is that the validateUser function returns a type of Observable<Object>, which will contain the JSON returned by the /validate-user endpoint. Unfortunately, we cannot simply return this result from our canActivate function, as we need to return a type of Observable<boolean>. To overcome this, we use the pipe function and the map and catchError operators from RxJS.

The pipe function is used to combine two functions into one, and will execute each of these functions in sequence. It returns a single function. So instead of using subscribe on the validateUser function, we create a new function using pipe that combines the map() and catchError() functions. A stripped-down version of how this is put together is as follows:

function usingPipe( subject: Observable<Object>) { 
    subject.pipe( 
        map(() => {}), 
        catchError((err) => { 
            return of(false); 
        }) 
    ); 
} 

Here, we have a function named usingPipe that takes a single parameter named subject of  type Observable<Object>We then use the pipe function on the subject argument, and chain together two functions.

The first function is created from the map function, and the second function is created from the catchError function. Note that both the map and the catchError functions use a function as an input argument. Where the map function will automatically return an Observable, the catchError function does not. This is why we need to use the of function to create an Observable from the boolean value of false.

The map function, or, more correctly, the map operator from the RxJS library is used to transform each of the items in an Observable using a function. As a simple example of this, consider the following code:

function usingMap(subject: Observable<Object>): Observable<boolean> { 
    return subject.pipe( 
        map((object: Object): boolean => { 
            return false; 
        })); 
} 

Here, we have a function named usingMap that has a single parameter named subject which is of type Observable<Object>This function, however, returns a type of Observable<boolean>. This means that each of the items in the input Observable stream need to be converted from an Object to a boolean. We are using the pipe function directly on the subject argument, and providing a simple function for map to use. Note the signature of this anonymous function. It accepts an input type of Object, and returns a boolean. This is how we can use pipe and map to transform an Observable stream from one type to another.

Our canActivate function is combining the pipe, map, and catchError functions into a single workflow in order to convert the result of the validateUser REST call from an Observable<Object> into an Observable<boolean>.

Our new version of the AuthGuard, therefore, is calling the REST endpoint at /validate-user, and sending in the token it has retrieved from Local Storage. If the endpoint returns success, then the AuthGuard knows that the token it used was successfully validated, and will allow access to the SecureComponent.

So we have seen how to create a JWT token, how to decode it, and how to verify it. We have also seen how we can use the power of Observables to transform data from one type to another.

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

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