Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 Implicit Flow Callback and Router Guard

What is the best way to handle the implicit flow Callback in Angular 4? I want the Guard to wait until the user is redirected back with the token and it is stored before the Guard returns true or false, I'm getting the Access Denied route for a few seconds before I'm redirected back to check the token. Is there a better way to handle the AuthGuard than what I'm doing so I don't get the Access Denied before authentication completes?

How do I make the router guard wait for the redirect?

AppComponent

    ngOnInit() {    

         //if there is a hash then the user is being redirected from the AuthServer with url params
         if (window.location.hash && !this.authService.isUserLoggedIn()) {     

          //check the url hash
          this.authService.authorizeCallback();

        }
        else if (!this.authService.isUserLoggedIn()) {         

          //try to authorize user if they aren't login
          this.authService.tryAuthorize();       

  }    
}

AuthSerivce

tryAuthorize() {
       //redirect to open id connect /authorize endpoint
        window.location.href = this.authConfigService.getSignInEndpoint();
    }

    authorizeCallback() {       

        let hash = window.location.hash.substr(1);

        let result: any = hash.split('&').reduce(function (result: any, item: string) {
            let parts = item.split('=');
            result[parts[0]] = parts[1];
            return result;
        }, {});


        if (result.error && result.error == 'access_denied') {
            this.navigationService.AccessDenied();
        }
        else {

            this.validateToken(result);
        }
    }


    isUserLoggedIn(): boolean {       
        let token = this.getAccessToken();

        //check if there is a token      
        if(token === undefined || token === null || token.trim() === '' )
        {
            //no token or token is expired;
            return false;
        }

        return true;
    }


    getAccessToken(): string {              

        let token = <string>this.storageService.get(this.accessTokenKey);


        if(token === undefined || token === null || token.trim() === '' )
        {
            return '';
        }

        return token;
    }

    resetAuthToken() {
        this.storageService.store(this.accessTokenKey, '');
    }

    validateToken(tokenResults: any) {        

        //TODO: add other validations         

        //reset the token
        this.resetAuthToken();

        if (tokenResults && tokenResults.access_token) {

            //store the token
            this.storageService.store(this.accessTokenKey, tokenResults.access_token);

            //navigate to clear the query string parameters
            this.navigationService.Home();

        }
        else {
            //navigate to Access Denied
            this.navigationService.AccessDenied();
        }

    }
}

AuthGuard

 canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot){

    var hasAccess = this.authService.isUserLoggedIn();        

    if(!hasAccess)
    {
        this.naviationService.AccessDenied();
        return false;
    }
     return true;    
  }
like image 338
Fab Avatar asked May 15 '17 19:05

Fab


2 Answers

If you want your guard to wait for asynchronous task, you need to change your AuthService to return an observable and emit value that you need inside of asynchronous task that you want to wait, in your case its reduce(). After that make a subscription in the guard. In this way you can make your guard wait for any asynchronous task.

AuthGuard

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot){

  this.authService.isUserLoggedIn().map(logged => {
        if (logged) {
            return true;
        } else {
            this.naviationService.AccessDenied();
            return false;
        }
    }).catch(() => {
        this.naviationService.AccessDenied();
        return Observable.of(false);
    });
  }
}

part of AuthService

 isUserLoggedIn(): Observable<boolean> {
    return new Observable((observer) => {
      let hash = window.location.hash.substr(1);

      let result: any = hash.split('&').reduce(function (result: any, item: string) {
        let parts = item.split('=');
        result[parts[0]] = parts[1];

        if (result.error && result.error == 'access_denied') {
          observer.next(false);
          observer.complete();
        }
        else {
          this.validateToken(result);
        }
        let token = this.getAccessToken();

        //check if there is a token      
        if(token === undefined || token === null || token.trim() === '' )
        {
          //no token or token is expired;
          observer.next(false);
          observer.complete();
        }

        observer.next(true);
        observer.complete();
      }, {});
    });
} 
like image 152
Yevgen Avatar answered Nov 06 '22 07:11

Yevgen


The CanActivate method can return an Observable, Promise, or Boolean, and Angular will know to unwrap it and handle everything async. You can alter your code to check for the necessary data before returning either a completed/failed Observable or a resolved/rejected Promise to the Angular router, and calling this.naviationService.AccessDenied() as a result of that asynchronous function.

like image 27
joh04667 Avatar answered Nov 06 '22 06:11

joh04667