I have a problem. So, I want to create a route guard in Angular that doesn't allow the user to reach login page if he is logged in. I check if he is logged in if the BehaviourSubject from the AuthService emits a new user object, but when I type in the search bar the URL of the login page, the user object emitted by the subject become empty. Do you know why is this happening?
@Injectable({
providedIn: 'root'
})
export class LoggedInGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router){}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.authService.user$.pipe(map(user => {
// check if the user object is empty
const isAuth = !(!!Object.keys(user).length);
console.log(isAuth)
if(isAuth) {
return true;
}
// navigate to /books if user is logged in
return this.router.createUrlTree(['/books']);
}));
}
}
Here is my AuthService logic:
export class AuthService {
private baseUsersUrl =
environment.firebase.databaseURL + MAIN_API_ENDPOINTS.users;
private userSource = new BehaviorSubject<User>(<User>{});
user$ = this.userSource.asObservable();
tokenExpirationTimer!: any;
constructor(private httpClient: HttpClient,private afAuth: AngularFireAuth, private router: Router) { }
login(email: string, password: string){
return this.httpClient.post<AuthResponseData>(MAIN_API_ENDPOINTS.login,{
email: email,
password: password,
returnSecureToken: true
}).pipe(
catchError(errorResponse => {
console.log(errorResponse);
let errorMessage = 'An unknown error occured!';
if(!errorResponse.error || !errorResponse.error.error) {
return throwError(errorMessage);
} else {
errorMessage = 'Email or password is incorrect'
}
return throwError(errorMessage);
}),
tap((resData) => {
this.saveUserData(email, resData.localId, resData.idToken, +resData.expiresIn);
}),
);
}
saveUserData(email: string, localId: string, idToken: string, expiresIn: number) {
const expirationDate = new Date(new Date().getTime() + expiresIn * 1000);
const currentUser: User = {
email,
id: localId,
token: idToken,
tokenExpirationDate: expirationDate
};
//token will expire in 1h
this.autoLogout(expiresIn * 1000);
document.cookie = 'token' + '=' + idToken;
}
autoLogin() {
const idToken = document.cookie.split('=')[1];
if(!idToken) {
return;
}
this.getUserData(idToken).subscribe((user) => {
this.saveUserData(user.users[0].email, user.users[0].localId, idToken, 100);
})
}
getUserData(idToken: string) {
return this.httpClient.post<any>(
MAIN_API_ENDPOINTS.userData,
{idToken: idToken});
}
And I added this route guard to the route this way:
const routes: Routes = [
{
path: 'login',
component: LoginComponent,
canActivate: [LoggedInGuard]
}]
Whenever a route is typed in the search bar of the browser, the angular application bootstraps itself with a clean slate. What you actually have to do is try to call the
autoLogin()before your code reaches theLoggedInGuard. Whenever a route is hit in an angular application,canActivateis the first thing that runs, if present on the route. I believe yourAuthServiceruns after theLoggedInGuardhas finished performing its logic, due to whichLoggedInGuardfails to perform the Login Route Validation and let the route open. If you want to try it, you can also try refreshing the browser window when you are on the login page. You will face the same scenario.