Search code examples
angularasp.net-core-mvcangular2-routingangular2-guards

Update Angular2 Service boolean before routing after bootstrapping


I'm using Angular2 with ASP.NET Core MVC and managing manual URL navigation works fine, the server is loading my Home view with Angular2 successfully.

On user authentication, I'm setting up a session variable like this :

HttpHelper.HttpContext.Session.SetString("isLoggedIn", true.ToString() );

What I want is that after the user is in the application, if somewhat he wants to load a specific route by manually navigating to it, I want my service to call my ASP Controller to check if the user already got authenticated so that my guard allows the routing. Instead, the guard is by default set to false and I obviously get redirected to my login page.

Here is my ASP Controller method I want to call to update my IsLoggedIn value in my auth.service :

[HttpGet]
public IActionResult IsConnectedState()
{
    if (!String.IsNullOrWhiteSpace(HttpHelper.HttpContext.Session.GetString("isLoggedIn")))
        return Ok(true);
    else
        return Ok(false);
}

so that my AuthenticationGuard can call the AuthenticationService to update the boolean managing the authenticated state : EDIT : pasting the whole auth.guard.ts code for clearer explanation

@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {
    if (!this.authService.isLoggedIn) {
        this.authService.setupLoggedInState().subscribe(() => {
            alert("guard check : " + this.authService.isLoggedIn);
            });
        }
    }

    canActivate()
    {
        if (this.authService.isLoggedIn) {
            return true;
        }

        this.router.navigate(['/login']);
        return false;
    }
}

with the following code updating the boolean in my auth.service :

setupLoggedInState() {
    alert("Setting Up");

    // Setting up the Http call 
    let lControllerAction: string = "/IsConnectedState";
    let lControllerFullURL: string = this.controllerURL + lControllerAction;
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    // Call my ASP Controller IsConnectedState() method
    return this.http.get(lControllerFullURL, options)
        .map((res: any) => {
            // Réponse reçue du WebService
            let data = res.json();
            alert(data);
            if (data == true) {
                this.isLoggedIn = true;
            }
        }
        ).catch(this.handleError);
}

The problem comes when I try to manually load a guarded route, I get redirected to the login page before the boolean in my service is updated. I don't know what kind of solution I can setup to manage this problem...

I tried calling

if (!this.authService.isLoggedIn) {
    this.authService.setupLoggedInState().subscribe(() => { });
}
  • in my guard constructor
  • in my service constructor

But it's still not doing it at the right moment. My best guess would be to manage this when I load my first AppModule, but even if this might be the right moment, I have no guarantee my boolean will be updated by the time Angular2 finished bootstrapping, and doing a synchronous Http call seems to be a really bad solution, because it would block the browser... Any idea would be really helpful.

PS : I did a similar post today, but it was for a different problem, so I'll link it here to avoid confusion : Managing user authenticated state on manual URL navigation


Solution

  • Calling subscribe() doesn't automatically give the value. If this invokes a request to the server, it takes a loooong time until the response arrives. The browser continues to execute your code and when eventually the response from the server arrives, the callback passed to subscribe(...) is called by the observable. You need to make your AuthGuard wait for the observable or actually the router by returning the observable from canActivate() so the router can wait for the observable to emit a response.

    @Injectable()
    export class AuthGuard implements CanActivate {
    constructor(private authService: AuthService, private router: Router) {
      canActivate() {
        return this.authService.checkLoggedIn
        .map(loggedIn => {
          if(loggedIn) {
            return true;
          } else {
            this.router.navigate(['/login']); // not tried myself to navigate here
            return false;
          }
        });
      }
    }
    
    
    @Injectable()
    class LoginService {
      constructor(private http:Http) {}
    
      checkLoggeIn() {
        return.http.get(someUrl).map(val => val.json());
      }
    }
    

    See What is the correct way to share the result of an Angular 2 Http network call in RxJs 5? for more details about how to cache results from HTTP requests.