0

I have an Angular application with SSR where all routes are protected and the Router initialNavigation property is set to enabled. I'm using the angular-oauth2-oidc package with Code Flow + PKCE. All routes are lazy loaded and I have a CanActivateChild guard checking if the user is authenticated.

When in the browser, the CanActivateChild checks if it's authenticated and if it's not, it redirects to an Azure AD B2C to perform the authentication. This is working fine when running without SSR.

When in the server, the CanActivateChild if it's not authenticated, it just cancels the navigation by returning false but there is no redirection. The idea is to return with no activated route, just the root component rendered/bootstrapped, since it's not authenticated and let the browser handle the authentication. I leave that to the browser since the angular-oauth2-oidc is for the browser platform, some functionalities in the OAuthService don't work on the server since it depends on some browser APIs, I do have an OAuthStorage implementations for each platform so I can get the token on each platform. With this setup, the Express.js server does not to respond, which I assume is because the bootstrapping of the application is waiting for the navigation to complete since initialNavigation is enabled. I was expecting this to work fine, since a cancellation in a sense, is a completion of the navigation as well.

I can think on a couple of ways to workaround this, like disabling initialNavigation which is not good since this will cause the page to flicker (so it's a no), or redirecting to an empty component just to complete the navigation, or manually redirect in the server to the Azure AD B2C authorize endpoint, etc.

Has anyone came across this and has a better solution? Am I missing something or am I configuring something wrong?

Edit, adding some snippets:

AuthGuard:

canActivateChild(
    _: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.authService.isAuthenticated$.pipe(
      tap((isAuthenticated) => isAuthenticated || this.authService.login(state.url)),
    );
  }

The authService.isAuthenticated$ is a an Observable from a BehaviorSubject which in the server just sends if the token is in the cookies. I verified and in the server is successfully returning false when there is no token stored and true otherwise. When it's true everything works fine since the navigation completes.

The authService.login method in the browser calls the OAuthService.initCodeFlow method. In the server it's an empty method.

Leosvel
  • 71
  • 8
  • can you share the code of the guard here? – Ayyash May 12 '20 at 12:49
  • @Ayyash I edited the question with more details. – Leosvel May 12 '20 at 12:59
  • did you try wrapping the if statement with the platform check? do the if statement only on browser and return false on server? – Ayyash May 12 '20 at 13:03
  • @Ayyash what if statement? if you refer to what's inside the `tap` operator, that operator doesn't affect the stream, so it does effectively return `false` when there is no token in the request cookies. As I said above, when the token is there it does run fine. – Leosvel May 12 '20 at 13:08
  • If the platform is browser return isAuthenticated$ else return of(null), may be? – Ayyash May 12 '20 at 13:13
  • @Ayyash that won't do, if I do that then I won't ever process the route on the server if the user is authenticated, which like I said, it's a scenario that's working fine. The `AuthGuard` is definitely not the problem here, it's correctly returning `true` or `false` depending on the authentication status. I believe the issue is Angular expects a route to be resolved in the initial navigation, otherwise it just doesn't bootstrap the root component. According to the docs, setting `initialNavigation` to `enabled` do block the bootstrapping until navigation is completed. – Leosvel May 12 '20 at 13:22

0 Answers0