What I want to achieve: I want to share authentication state across my application using BehaviorSubject. I use the authentication state e.g. inside an auth-guard to prevent the user from visiting login/register pages when the user already is authenticated.
Problem: because the BehaviorSubject has a initial value, which is false (not logged in), it seems that the auth-guard takes this first value, instead of waiting for the uid-sync.
AuthInfo (Auth state store):
export class AuthInfo {
constructor(public uid: string) {}
isLoggedIn() {
return !!this.uid;
}
}
AuthService:
@Injectable()
export class AuthService {
static UNKNOWN_USER = new AuthInfo(null);
authInfo$: BehaviorSubject<AuthInfo> = new BehaviorSubject<AuthInfo>(AuthService.UNKNOWN_USER);
constructor(private af: AngularFire) {
this.af.auth.subscribe(auth => {
if (auth) {
console.log('got the uid');
this.authInfo$.next(new AuthInfo(auth.uid));
} else {
this.authInfo$.next(AuthService.UNKNOWN_USER);
}
});
}
logIn(email: string, password: string): Promise<FirebaseAuthState> {
return this.af.auth.login({email: email, password: password});
}
}
AuthGuard:
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService,
private router: Router) {
}
canActivate(): Observable<boolean> {
return this.authService.authInfo$.map(authInfo => {
if (authInfo.isLoggedIn()) {
this.router.navigate(['/user'])
}
return !authInfo.isLoggedIn();
});
}
}
So canActivate is processed with authInfo.isLoggedIn()
being false
and after a fraction of a second I see Got the uid
in the console. Any ideas how to prevent the first false
? I think that BehaviorSubject is correctly used here, because it allows us to set an initial state. However the auth-guard will always receive false (the initial value). Right after that the
this.authInfo$.next(new AuthInfo(auth.uid));
will trigger, when the canActivate method was already finished.
Guard's canActivate method, as its name suggests, resolves upon the attemption to activate specific route.
As I understand from the code provided, you're trying to redirect user to the /user
route upon retrieving auth uid
from the server. To achieve that, you need to initiate redirecting to the desired route once the auth uid
is retrieved - e.g. after logging in, and let your guard do its job, enable or deny the access to the route.
After sorting things out, here is the walk-through the changed code and structure:
AuthInfo class:
// No changes.
AuthService:
@Injectable()
export class AuthService {
static UNKNOWN_USER = new AuthInfo(null);
authInfo$: BehaviorSubject<AuthInfo> = new BehaviorSubject<AuthInfo>(AuthService.UNKNOWN_USER);
constructor(private af: AngularFire) { }
logIn(email: string, password: string): Promise<FirebaseAuthState> {
return this.af.auth.login({email: email, password: password});
}
getAuthInfo(): Observable<AuthInfo> {
return this.af.auth.map(auth => {
if(auth) {
console.log('got the uid');
let authInfo = new AuthInfo(auth.uid);
this.authInfo$.next(authInfo);
return authInfo;
}
else {
this.authInfo$.next(AuthService.UNKNOWN_USER);
return AuthService.UNKNOWN_USER;
}
});
}
}
AuthGuard:
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService,
private router: Router) {
}
canActivate(): Observable<boolean> | boolean {
// get the most recent value BehaviorSubject holds
if (this.authService.authInfo$.getValue().isLoggedIn()) {
// can access targeted route
return true;
}
/*
User is not logged in as stored authInfo indicates,
but in case the page has been reloaded, the stored value is lost,
and in order to get real auth status we will perform the server call,
(authService.getAuthInfo method will automatically update the BehaviorSubject value,
and next time the protected route is accessed, no additional call will be made - until
the next reloading).
*/
return this.authService.getAuthInfo()
.map((authInfo: AuthInfo) => {
if(authInfo.isLoggedIn()) {
// can access targeted route
return true;
}
this.router.navigate(['login']); // redirect to login screen
return false;
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With