最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

authentication - Angular NGXS Auth Guard: How to solve Race Condition with User State - Stack Overflow

programmeradmin13浏览0评论

I’m experiencing a race condition when using an auth guard with NGXS state management in an Angular application. The guard should check if the user is authenticated and has the required roles before granting access. However, the user state is null initially, causing unwanted redirects to "/forbidden" even when the user is eventually loaded. Here’s my current implementation:

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { SecurityState } from './components/state/security.state';
import { map } from 'rxjs';

export const authGuard: CanActivateFn = (route, state) => {
  const roles = route.data["roles"];
  console.log("before return");
  const router = inject(Router);

  return inject(Store).select(SecurityState.getCurrentUser).pipe(
    map(user => {
      console.log(user);
      if ((!roles && user) || user?.roles?.some(userRole => roles?.includes(userRole))) {
        return true;
      }
      router.navigate(["/forbidden"]);
      return false;
    })
  );
};

I’m experiencing a race condition when using an auth guard with NGXS state management in an Angular application. The guard should check if the user is authenticated and has the required roles before granting access. However, the user state is null initially, causing unwanted redirects to "/forbidden" even when the user is eventually loaded. Here’s my current implementation:

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { SecurityState } from './components/state/security.state';
import { map } from 'rxjs';

export const authGuard: CanActivateFn = (route, state) => {
  const roles = route.data["roles"];
  console.log("before return");
  const router = inject(Router);

  return inject(Store).select(SecurityState.getCurrentUser).pipe(
    map(user => {
      console.log(user);
      if ((!roles && user) || user?.roles?.some(userRole => roles?.includes(userRole))) {
        return true;
      }
      router.navigate(["/forbidden"]);
      return false;
    })
  );
};
Share Improve this question asked Mar 17 at 16:35 Ilias BglIlias Bgl 111 bronze badge
Add a comment  | 

2 Answers 2

Reset to default 0

Use the filter rxjs operator to filter out the undefined value or the falsy value, so that the first proper emission is only considered.

export const authGuard: CanActivateFn = (route, state) => {
  const roles = route.data["roles"];
  console.log("before return");
  const router = inject(Router);

  return inject(Store).select(SecurityState.getCurrentUser).pipe(
    filter(user => !!user), // allow next step only if user has a value.
    map(user => {
      console.log(user);
      if ((!roles && user) || user?.roles?.some(userRole => roles?.includes(userRole))) {
        return true;
      }
      router.navigate(["/forbidden"]);
      return false;
    })
  );
};

Before accessing the store, please, make sure your app waits for the store to be fully loaded (if coming from an API) or use a library to store it and keep it in sync in localStoage. ie. ngrx-store-localstorage

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(
    private store: Store<IStore>,
    private router: Router,
  ) {}

  canActivate(_: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.store.select(selectCurrentUser).pipe(switchMap((currentUser) => {
      // @NOTE: We can also check for user role and permissions here.
      if (currentUser) {
        return of(true);
      }

      this.redirectToLogin(state.url);
      return of(false);
    }))
  }

  redirectToLogin(returnUrl: string): void {
    this.router.navigate(['/sign-in'], { queryParams: { returnUrl } });
  }
}
发布评论

评论列表(0)

  1. 暂无评论