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
2 Answers
Reset to default 0Use 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 } });
}
}