isAuthenticated fails on login, works on refresh

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.

2 Likes

Brandon, thank you for the detailed feedback you have shared and for bringing this up. I apologize in advance for any blocker or speed bump the guidance may have caused.

I myself spotted that issue as well a few days ago and got in touch with our SDK team to discuss this in more detail. I just now also shared your post :pray: I’ll share with you what they say about this and any changes we may make on the guidance.

Thank you for using Auth0 and it’s pretty cool that you are writing an npm library to make it easier for your company to use our service :muscle:

Hi Dan,

Thanks for the reply, and sorry for being so pissy when I wrote my comment, I was just tired and frustrated.

Let me know what the SDK team says. While I think my solution “works”, its pretty dirty and confusing from a maintainability standpoint. I’m going to be looking into a more elegant solution today.

1 Like

No worries! I understand, I think most (read: all of us) devs have been there at some point before :sweat_smile:

I’ll let you know what my teammates say about the authentication flow in Angular. Quick side question on that: when you login to your Angular application using Auth0, do you get a flash of the home page or whatever page where the authentication process was started and then it renders the protected page?

I haven’t noticed anything like that, but I’m also not following the guide when it comes to the implementation of the localAuthSetup and handleAuthCallback functions. I believe the way its currently set up means you can’t predict when the service is actually instantiated. Angular treeshakes services that are provided but not injected, so those 2 functions in the constructor aren’t going to run until the first component that needs the auth service requests it from the DI injector. That might be interrupting your flow and causing the flashing.

Just for my sanity’s sake I left localAuthSetup in the app.component (which means the auth0 service is always instantiated at the exact same point in the life cycle) and the handleAuthCallback function in my callback component located at the route “/callback”.

1 Like

:100: That’s how I feel I want to approach it too!

I’m doing some testing now, but I think this is going into version 2.0.0 of our @sigao/ng-auth0 library.

I abandoned the observable public properties on the service in favor of observable factory functions. Also found a pretty elegant way to ensure that the “isAuthenticated” stream is fresh both on initial subscription as well as able to accept pushed values from the handleAuthCallback response (which solves the initial problem).

the “_auth0ClientStream” variable is set up exactly like the docs with the “shareReplay” thing.

  public getAuth0Client(): Observable<Auth0Client> {
    return this._auth0ClientStream;
  }

  public getHandleRedirectCallback(): Observable<RedirectLoginResult> {
    return this.getAuth0Client()
      .pipe(concatMap((client: Auth0Client) => from(client.handleRedirectCallback())));
  }

  public getUserProfile(options?: GetUserOptions): Observable<any> {
    return this.getAuth0Client().pipe(
      concatMap((client: Auth0Client) => from(client.getUser(options))),
      tap(user => this.userProfile = user)
    );
  }

  public getIdTokenClaims(options?: GetIdTokenClaimsOptions): Observable<any> {
    return this.getAuth0Client().pipe(
      concatMap((client: Auth0Client) => from(client.getIdTokenClaims(options))),
      tap(claims => this.idTokenClaims = claims)
    );
  }

  public getTokenSilently(options?): Observable<string> {
    return this.getAuth0Client().pipe(
      concatMap((client: Auth0Client) => from(client.getTokenSilently(options))),
      tap(token => this.token = token)
    );
  }

  public getIsAuthenticated(): Observable<boolean> {
    return this.refreshAuthentication()
      .pipe(concatMap(() => this._isAuthenticatedStream));
  }

  private refreshAuthentication(): Observable<boolean> {
    return this.getAuth0Client()
      .pipe(
        concatMap((client: Auth0Client) => from(client.isAuthenticated())),
        tap(val => this._isAuthenticatedSubject.next(val))
      );
  }

Hopefully this helps any future users.

@dan-auth0 is there any follow up on this? It seems to have just gone quiet. We are also having the same problem. I will try implementing the workaround but it would be nice to have an official response to this (and maybe for the quickstart guide to be updated).

1 Like

Howdy, Nick! Thank you for joining our Auth0 Community and bringing light to this issue again. I have been following up internally on this matter and raising the questions and concerns with the appropriate team.

The result of our goal to improve the Auth0 integration experience for Angular developers is the new Auth0 Angular SDK, which is in beta :slight_smile:

This SDK should solve all these troubles. Nick, if you’d like, we are going to host an Angular SDK workshop for developers on Monday 24th August @ 11am PST / 7pm BST. If you’d like to attend, I can send you an invitation. @brandon_sigao, you are also welcome to attend, if you’d like. We are looking to get as much feedback as possible from developers who work with Angular and Auth0 so that we can provide you with an excellent developer experience.

1 Like

Oh yeah, please send me an invitation, that would be great.

Sweet! Let me send you a message with details :slight_smile:

I’ll take an invite as well. Is it one per person or could I share it with other people in my organization?

Hi Dan, I’m seeing the same problem and I’m using the React SDK. Do you know if a fix is in the works? Are there any instructions to work around it in the meantime?

1 Like

Howdy, Dhawkings! Could you give me a recap of the problem you are experiencing, please?

Hi Dan.

I’ve just used the quickstart directions found at https://auth0.com/docs/quickstart/spa/react to implement a universal login. It is working but I’ve had to create a couple ugly hacks to get around the fact that the isAuthenticated() call is returning false even though the user has just finished a successful sign in. Here are some more details:

I have my Auth0Provider component wrapping the app configured as follows:
<Auth0Provider
domain={AUTH_CONFIG.domain}
clientId={AUTH_CONFIG.clientId}
redirectUri={AUTH_CONFIG.callbackUrl}
audience={https://${AUTH_CONFIG.domain}/api/v2/}
scope=“read:current_user update:current_user_metadata”
>

I have the onClick for the Sign In link calling loginWithRedirect() from the @auth0/auth0-react package.

After the callback, when my user profile component has loaded, the isAuthenticated value returned by the following statement:
const { isAuthenticated, getAccessTokenSilently } = useAuth0();

is coming back false. I’m calling this statement in the useEffect hook for my component. In the same block of code, I can successfully retrieve the access token using the getAccessTokenSilently method so the isAuthenticated value should definitely be true.

Interestingly, as soon as I reload the page, it does come back as true. Right now, I’m calling window.page.reload to make it work but I won’t actually deploy it like that. Need to nicer way to do it. Ideally, I’d like to figure out how to get isAuthenticated to come back with an accurate value.

1 Like

Hey, Dhawkings! I have been doing research on this. Are you using isLoading within your app tree to ensure that the components that consume isAuthenticated, user, etc. get those variables only after the SDK has finished loading?

I am having a similar issue. It successfully logins, works OK.
However, when I refresh the page,
the isAuthenticated always becomes false (even though I am taking care of isLoading).

{isLoading && <Spinner />}
{!isLoading && (
          isAuthenticated ? (
            <UserDropdown
              user={user}
              handleLogout={logoutWithRedirect}
            />
          ) : <span>Login</span>
        )}
1 Like

Hey, I’m having a similar issue. I think the cause for @brandon_sigao frustration is that the Angular/JS Example for using the Auth0 SPA Library assumes that handleRedirectCallback is called on a dedicated Callback page where you dont use isAuthenticated to protect the Route. This leads to a successful Token Exchange and then a redirect to the guarded page using isAuthenticated (true).

Currently Im experiencing an Issue where everything works as expected in Chrome and Firefox, but doesnt in Chrome Incognito nor Safari. On Chrome I get a successful Token Exchange and isAuthenticated returns true initially. But in Incognito or Safari isAuthenticated is called two times with false value which causes the App to redirect again to the Login Page. That basically leads to the situation where every Page Reload triggers a new Auth Flow (Redirects). I suspect that might be related to some Security/Cookie Stuff in the Browser itself maybe?

Im following also the Auth0 Example for Angular: setup Client, call handleRedirect and localAuthSetup, wait for Client to be initialised and call client.isAuthenticated()

Edit 1: Chrome Incognito comes with a new Feature enabled by Default (“Block third Party Cookies”). Disabling this Features makes it work in Chrome Incognito again. Still wondering how to get it working in Safari. It seems to block Third Party Cookies by Default. But I cant find any Console Output or other Warnings related to it. I can also see the Cookies being set in both Chrome and Incognito. Its really confusing.

Edit 2: Safari blocks Third Party Cookies by default. https://developer.apple.com/documentation/safari-release-notes/safari-13_1-release_notes

So how its supposed to work for Chrome Incognito and Safari at all? Custom Domain?

2 Likes

Howdy, Azamat! Thank you for joining our Auth0 Community and for sharing this important feedback.

May I ask you if you could please report this bug in the repo as an issue?

That way the team working on that SDK can track it and address it accordingly. I would strongly suggest if you could also perhaps provide us with a repo that reproduces the issue and reproduction steps. Having a CodeSandbox test project may also help – just ensure to remove your own Auth0 config variables :slight_smile:

Howdy, Tim! Thanks for joining the Auth0 Community. To ensure that I better understand your situation, the issue that you are experiencing relates to an Angular application that is using the Auth0 SPA SDK to implement user authentication, correct? Have you tried the new Auth0 Angular SDK, yet?

You are on the right track with your research and thank you for sharing your knowledge. Cookies can indeed cause some issues! :cookie: :milk_glass: They are so delicious in real life but can be so problematic sometimes in the digital world hehe