1

i don't have an login page , this is sso call

In the app component, I have dispatched an action to load the server-side token call and get the token and based on the user roles I can activate the guard in routing. if guard activates return true, I will go the dashboard page.

In the dashboard page, I have a refresh in the browser URL, can activate execute before the login action effects, therefore can activate return false and page displays blank.

In the dashboard have child routes, there also have some child links like products dashboard/products, there also have activated guard, but can activate return false before the login dispatch action effects success call.

Can anyone help me what I am doing wrong?

here is routing,

  {
    path: '',
    component: HomeComponent,
    canActivate:[AuthGuard],
    children: [
      {
        path: 'product',
        loadChildren: () => productModule
      },
      {
        path: 'order',
        loadChildren: () => orderModule
      }
    ]
  },

here is activate method: here i get the success from effects call while dispatch an action from app.component:

  canActivate(): Observable<boolean> {
     return this.store.select(appState => appState.auth.isAuthenticated)
      .pipe(map(authUser => {

        if (!authUser) {
          return authUser;
        }
        return authUser;
      }))
    }

App.componet:

ngOnInit(){



   this.onSubmit();

}

onSubmit(): void {
  const payload = {
    email: 'test@test.com',
    password: 'admin'
  };
  alert('dispatch an action');
  this.store.dispatch(new LogIn(payload));
}

the problem only in a page refresh. when clicking on the router link, the logic works fine.

my initial state :

export const initialState: State = {
  isAuthenticating:false,
  isAuthenticated: false,
  user: null,
  errorMessage: null
};

screenshot of flow:enter image description here

Mohamed Sahir
  • 2,482
  • 8
  • 40
  • 71

2 Answers2

6

I might have a solution for this.

Basically you can set a property in your initial state to indicate that you're initializing:

export interface State {
  isAuthenticated: boolean;
  isInitializing: boolean;
}

const initialState: State = {
  isAuthenticated: false,
  isInitializing: true
}

And when you dispatch a login or logout, make sure to set isInitializing to false in the reducer:

const _authReducer = createReducer(
  initialState,

  on(AuthActions.setAuthenticated, (state, action) => ({
    ...state,
    isAuthenticated: true,
    isInitializing: false,
  })),

  on(AuthActions.setUnauthenticated, (state, action) => ({
    ...state,
    isAuthenticated: false,
    isInitializing: false,
  }))
);

In your guard, you can then use a filter to wait for isInitializing to be set to false, before returning information about the user authentication:

canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    console.log('Authguard running...');

    return this.store.select('auth').pipe(
      map(authState => authState.isInitializing),
      // tap((isInit) => {
      //   console.log('isInit: ' + isInit);
      // }),
      filter((isInit) => !isInit),
      switchMap(() => {
        return this.store.select('auth').pipe(
          map(authState => authState.isAuthenticated)
          // tap((isAuth) => {
          //   console.log('isAuth: ' + isAuth);
          // }),
          map((isAuth) => {
            if (isAuth) return isAuth;
            return this.router.createUrlTree(['/auth']);
          })
        );
      })
    );
  }

You can remove the comments from the tap operators if you want to monitor these values in the console :)

I'm using this solution to be able to reload on a specific url when logged in. If you try to visit that same page when not logged in you are redirected to the login.

jartecken
  • 61
  • 1
  • 4
0

I think you have a problem with the asynchronism. When you refresh the page, the store should init the state with false and the guard check occurs before you get the response from your backend. There are many possible solutions, but based on the code and guard that you expose I will do the following:

Create a flag in the store to know if you are waiting the response of the authentication, like isAuthenticating then in your guard could do something like:

canActivate(): Observable<boolean> {
        return new Observable(observer => {
           this.store.select(appState => appState.auth.isAuthenticating)
               .pipe(filter(isAuthenticating => !isAuthenticating))
               .subscribe(() => {
                  this.store.select(appState => appState.auth.isAuthenticated).pipe(
                      take(1)
                  ).subscribe(isAuthenticated => {
                      observer.next(isAuthenticated);
                      observer.complete();
                  })
          })
     }
   }

I hope this help

Lemon
  • 148
  • 10
  • yes u r right guard is loaded before success response back, I have already set isAuthenticated is false,once login sucess isauthenticated true ,but i have tried with ur code still is authenticating is false as well as isAuthenticated also false.not working above one – Mohamed Sahir May 02 '20 at 17:28
  • the problem here is login success reducer is executed after can activate guard,when executing can activate ,isauthentcating and authenticated are false due to it is initial state,once observer.complete it executes login sucess reducer, there i have set isauthenticated true – Mohamed Sahir May 02 '20 at 17:48
  • Are you initializing isAuthenticating = false and settting it to true when the login success or error comes? If you control the completion of the observable in canActivate guard, the redirection should not resolve until it completes. So if you emit isAuthenticated when you are sure that is the correct value you won't have any troubles. – Lemon May 02 '20 at 19:28