I have an Angular application that’s used internally within our intranet. The requirement is straightforward: users should be authenticated with Azure Active Directory as soon as they access any URL within the app. There’s no need for a dedicated login or logout page. The authentication should be seamless and automatic. Here’s what I’ve done so far:
I dispatch the login action in the ngOnInit method of the AppComponent. Here’s the code:
export class AppComponent implements OnInit {
constructor(private store: Store<AppState>) {}
ngOnInit(): void {
this.store.dispatch(AuthActions.loginProcess());
}
}
I think with an AuthGuard this is unnecessary because I guess it will dispatched within the guard (?)
The effect, action, and selector look like this:
auth.actions:
export const loginProcess = createAction('[Auth] Login');
export const loginSuccess = createAction('[Auth] Login Success', props<{ user: User | null }>());
export const loginFailure = createAction('[Auth] Login Failure', props<{ error: any }>());
auth.effects:
loginStart$ = createEffect(() =>
this.actions$.pipe(
ofType(AuthActions.loginProcess),
mergeMap(() => from(this.authService.initialize()).pipe(
map(user => {
return AuthActions.loginSuccess({user});
}),
catchError(error => of(AuthActions.loginFailure({ error })))
))
)
);
auth.selectors:
export const selectAuthState = createFeatureSelector<AuthState>('auth');
export const selectIsLoading = createSelector(
selectAuthState,
(state: AuthState) => state.isLoading
);
export const selectUser = createSelector(
selectAuthState,
(state: AuthState) => state.user
);
export const selectAuthError = createSelector(
selectAuthState,
(state: AuthState) => state.error
);
When I need the user in another component, I subscribe to the user state. E.g.:
export class List4Component implements OnInit {
constructor(private store: Store<AppState>) {
this.isLoading$ = this.store.select(Liste4Selectors.selectIsLoading);
this.liste4$ = this.store.select(Liste4Selectors.selectListe4);
this.error$ = this.store.select(Liste4Selectors.selectAuthError);
this.user$ = this.store.select(AuthSelectors.selectUser);
}
//...
ngOnInit(): void {
this.user$.subscribe(value => this.store.dispatch(Liste4Actions.fetchList()));
this.liste4$.subscribe(data => {
if (data) {
this.dataSource.data = data;
this.updateDisplayedColumns(data);
}
});
}
However, I believe it’s better practice to handle this with a guard. The problem I’m facing is that the guard seems to execute before the authentication process is complete, causing it to redirect to an empty or incorrect route.
Current AuthGuard:
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private store: Store<AppState>, private router: Router) {}
canActivate(): Observable<boolean> | Promise<boolean> | boolean {
return this.store.select(selectIsLoading).pipe(
filter(loading => !loading),
take(1),
switchMap(() => this.store.select(selectUser)),
map(user => {
return !!user;
})
);
}
}
What I need is for the guard to check if the user is authenticated when a component is accessed. If the user is authenticated, allow access to the route. If not, dispatch the authentication action, wait for the process to complete, and then allow access and lead him to the route.
Could someone help me figure out how to properly set up this guard so that it ensures the user is authenticated before granting access to the route?
Thanks in advance!