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.