0
votes

i am using adal-angular4 library for implicit flow implementation in an angular 4 single page application. This app calls a web api to display results. Both applications are hosted in azure (in a specific tenant) and registered appropriately and the configuration works.

The issue i face is when the first call to the API is made the acquiretoken does not immediately come back with the token, it errors out. As part of error handling this makes the page error (conscious decision on my part). But in a few seconds the acquiretoken returns the token. so if i refresh the page after a couple of seconds the api call succeeds and everything works as expected.

Here is the relevant code for various components

1. AuthenticationGuard.ts 

import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Router, CanActivate, CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, NavigationExtras } from '@angular/router'; import { AdalService } from 'adal-angular4';

@Injectable() export class AuthenticationGuard implements CanActivate, CanActivateChild {

constructor(
    private router: Router,
    private adalSvc: AdalService
) { }

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (this.adalSvc.userInfo.authenticated) {
        return true;
    } else {
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
        return false;
    }
}

canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.canActivate(childRoute, state);
}}
  1. appcomponent.ts

    import { Component, OnInit } from '@angular/core'; import { AdalService } from 'adal-angular4'; import { Router } from '@angular/router'; import { environment } from '../environments/environment';

    @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'My portal'; isLoggedIn = false; loggedInUser: string; private bodyTag: any; constructor(private adalService: AdalService, private router: Router) { this.adalService.init(environment.azureConfig); this.bodyTag = document.getElementsByTagName("body") }

    ngOnInit() { console.log("AppComponent Init"); this.adalService.handleWindowCallback(); if (this.adalService.userInfo.authenticated) { this.isLoggedIn = this.adalService.userInfo.authenticated; this.loggedInUser = this.adalService.userInfo.profile.name; } document.addEventListener("keydown", () => this.handleEvt); }

    logout(): void { this.adalService.logOut(); }}

  2. LoginComponent.ts

    import { Component, OnInit, Injectable, Inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AdalService } from 'adal-angular4'; import { APP_CONFIG, AppConfig } from '../app.constants';

    @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit {

    isLoggedIn = false; loggedInUser: string; localLoggedInUser: any; tokenNew: string;

    constructor( private route: ActivatedRoute, private router: Router, private adalService: AdalService, @Inject(APP_CONFIG) private appConfig: AppConfig ) { }

    ngOnInit() { this.adalService.handleWindowCallback(); if (this.adalService.userInfo.authenticated) { this.isLoggedIn = this.adalService.userInfo.authenticated; this.loggedInUser = this.adalService.userInfo.profile.name ; } }

    login(): void { const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; if (this.adalService.userInfo.authenticated) { this.router.navigate([returnUrl]); } else { this.adalService.login(); } } }

  3. ListService.ts (responsible for making the first API call via the getAll() method.

    import { Injectable, OnInit, Inject } from '@angular/core'; import {Headers, Http, Response, RequestOptions} from '@angular/http'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import '../rxjs-extensions'; import { APP_CONFIG, AppConfig } from '../app.constants'; @Injectable() export class ProductService implements OnInit { headers: Headers; tokenNew: string; items: Products[]; loggedInUserName: string; loggedInUserEmail: string; baseUrl = 'https://myproductsapi/'; productListEndpoint = this.baseUrl + '/products'; constructor( private http: HttpClient, private httpBase: Http, @Inject(APP_CONFIG) private appConfig: AppConfig, private adalService: AdalService) { this.adalService.handleWindowCallback(); this.setLoggedInUserDetails(); }

    public getAllProducts(): Observable> { return this.httpBase.get(this.productListEndpoint, this.getRequestOptionsWithHeadersForHTTP()) .map(response => { const tmp = response.json(); return tmp; }); }

private getRequestOptionsWithHeadersForHTTP(): RequestOptions { this.attemptAcquireToken(); this.tokenNew = this.adalService.getCachedToken(this.appConfig.resourceApplicationId); const headersNew = new Headers({ 'Content-Type': 'application/json' }); headersNew.append('Authorization', 'Bearer ' + this.tokenNew); const options = new RequestOptions(); options.headers = headersNew;

return options; } private attemptAcquireToken() { this.tokenNew = this.adalService.getCachedToken(this.appConfig.resourceApplicationId); console.log('1. token from attemptacquiretoken ' + this.tokenNew); // this.tokenNew = null; if (this.tokenNew == null) { console.log('2. token new is null - trying to acquiretoken using adal'); this.adalService.acquireToken(this.appConfig.resourceApplicationId).subscribe( token => { console.log('3. token acquired ' + token); //this happens but only a few seconds later this.tokenNew = token; }, // never comes error => { console.log('4. Error when acquiring token'); //this is called straight away console.log(error); }); } }

How do i ensure that the token exists BEFORE making a call to the API - as it stands, the sequence of method calls made to retrieve product list is as follows

  1. Call getAllProducts() - this is the main method responsible for calling the API. This initiates nested calls to other methods.

  2. getAllProducts() calls getRequestOptionsWithHeadersForHTTP() to get RequestOptions (headers) to associate with the http request before firing the request to the api endpoint.

  3. getRequestOptionsWithHeadersForHTTP() calls attemptAcquireToken() to get the token from adal service.

  4. attemptAcquireToken() calls acquiretoken() - the method first checks if there is a cached token and if there isnt one, it calls acquiretoken to retrieve one. It is here that the call straight away executes the block of code in "error" lambda - thereby raising an error in the UI. after a few seconds the when the acquiretoken has retreived the token it executes the code in the "token" lambda.

This only happens the first time a user logs in and there is no token in cache. For subsequent attempts this works fine (retrieving cached token).

1

1 Answers

0
votes

You can use Promise or chain observable like this:

private attemptAcquireToken():Observable<boolean> {
    this.tokenNew = this.adalService.getCachedToken(this.appConfig.resourceApplicationId);
    console.log('1. token from attemptacquiretoken ' + this.tokenNew);
    // this.tokenNew = null;
    if (this.tokenNew == null) {
        console.log('2. token new is null - trying to acquiretoken using adal');
        this.adalService.acquireToken(this.appConfig.resourceApplicationId).subscribe(
          token => {
            ...
            return Observable.of(true);
          }, // never comes
          error => {
            ....
            return Observable.of(false);
    }  
}

private getRequestOptionsWithHeadersForHTTP(): Observable<RequestOptions> {
    this.attemptAcquireToken().subsribe(r => {
    if (r !== false) {
            ...
            options.headers = headersNew;
            return Observable.of(options);
        } else {
            return Observable.of(undefined);
        }
    });
}

and so on ...