I’m writing an Angular npm library to streamline Auth0 implementation for my company and running into a strange bug. I’ve left the “auth0Client$” and “isAuthenticated$” streams virtually unchanged from the example documentation, yet immediately after login the “isAuthenticated$” stream returns false when subscribed to. If i refresh the page, it works as expected. As far as I can tell, my flow is no different than the example: Login → Redirect to Auth0 → return to /callback route → configure/setup Auth0 client on app entry → run localAuthSetup.
localAuthSetup immediately calls the isAuthenticated& stream and if I put a tap function in the stream and console log the value its false when re-entering the app from login. However, if I add this to my app.component:
setTimeout(()=>{
this.authService.isAuthenticated$(console.log);
}, 2000);
it will return true, even on initial login. Which suggests to me that client.isAuthenticated() is immediately returning false on app entry, even when its returning back from a successful login.
Any ideas?
Edit
Well, I think i know why its doing this, but i don’t know why the example would tell you to set it up like this. isAuthenticated only works after handleRedirectCallback has been called, yet the isAuthenticated$ stream is public and open to be subscribed to immediately on app load, before handleRedirectCallback is complete. So everything subscribes to that stream on app load, gets a “false” result because handleRedirectCallback isn’t complete, and then once its complete all those observables have already completed and don’t re-poll for the new value.
I always assumed that they left isAuthenticated$ public because a lot of people like working with observables, but now I just think it was a mistake.
Edit 2
I’m pretty certain this fixed my issue, although I’m still bitter that the docs always have mistakes like this that I have to correct for:
// Create internal subject and stream
this._internalIsAuthenticatedSubject$ = new BehaviorSubject<boolean>(null);
const internalIsAuthenticatedStream = this._internalIsAuthenticatedSubject$.asObservable();
// Create standard authentication stream as per docs
const externalIsAuthenticatedStream = this.auth0Client$.pipe(
concatMap((client: Auth0Client) => from(client.isAuthenticated())),
tap(res => this.loggedIn = res));
// On login, client.isAuthenticated() will be false until handleAuthCallback
// is finished. This means that subscriptions that are used on app startup will return false on app load and won't get the
// authenticated value until refresh. To fix this, we patch in the internal stream which is updated after the handleAuthCallback
// finishes. That way the most fresh value is sent to the observers. On refresh, the internal stream will provide only the initial
// 'null' value which is filtered.
this.isAuthenticated$ = merge(internalIsAuthenticatedStream, externalIsAuthenticatedStream)
.pipe(filter(val => val !== null));
Then after “handleAuthCallback” completes I run:
this._internalIsAuthenticatedSubject$.next(loggedIn);
to push the most fresh logged in state out to all the observers.