I am trying to create a service that authenticates a user and stores the token in the localStorage. I want other services (specifically the auth guard) to be able to access the current user, so I have set up the constructor of the authentication service like this:
currentUserSubject: BehaviorSubject<Token>;
constructor(private http: HttpClient) {
this.currentUserSubject = new BehaviorSubject<Token>(JSON.parse(localStorage.getItem('currentUser')));
}
When a user logs in using a http POST request, if it's successful call the next
method on the currentSubject
like this:
return this.http.post<Token>(`${environment.identityServerUrl}/connect/token`, params, httpOptions)
.pipe(map(user => {
localStorage.setItem('currentUser', JSON.stringify(user));
console.log(user);
this.currentUserSubject.next(user);
}));
The problem is, it doesn't seem to work. If I check my auth guard, it just sees the user as null. Only when I refresh is it populated (because of the authentication service constructor).
My auth guard looks like this:
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { mapTo, take, filter } from 'rxjs/operators';
import { AuthenticationService } from './authentication.service';
@Injectable({ providedIn: 'root' })
export class AuthGuardService implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthenticationService
) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authenticationService.currentUserSubject.pipe(
filter(user => !!user),
mapTo(true),
take(1)
);
}
}
Does anyone know what I am doing wrong?
If I change my canActivate
method to this:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean|UrlTree {
var user = this.authenticationService.currentUserSubject.value;
console.log(user);
if (user) return true;
this.router.navigate(["login"],{ queryParams: { retUrl: route.url} });
return false;
}
It should work, but it just the console log shows null
and so it just stays on the login page.
I saw that you can get .value
from this article:
https://medium.com/@luukgruijs/understanding-rxjs-behaviorsubject-replaysubject-and-asyncsubject-8cc061f1cfc0
The subject next()
method is used to send messages to an observable which are then sent to your angular components that are subscribed to that observable.
You have only created a subject but you have not created an observable that your guard can subscribe to.
currentUserSubject: BehaviorSubject<Token>;
currentUser$ = this.currentUserSubject.asObservable();
constructor(private http: HttpClient) {
this.currentUserSubject = new BehaviorSubject<Token>(JSON.parse(localStorage.getItem('currentUser')));
}
By convention, observables have a $
sign at the end. Now that we have a created an observable which receives your subject's messages, we can subscribe to it in the guard.
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authenticationService.currentUser$.subscribe(user => {
console.log(user);
});
}
I'm also not sure why you are using the filter
, using map
should be sufficient.