Callback URL adding lots of crap. Also access token issues

I have two problems. First and foremost, when someone logs into my site, it is supposed to route the to http://mysite.com/main. However, instead, it routes them to http://mysite.com/main#access_token=l4jpn-Ze3ATMbw123456789jyriv09Lr&scope=openid profile email&expires_in=7200&token_type=Bearer&state=ID7shkBDlJpbo0w.HIdTAU72kA5Ntw1B&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1123456789CI6Ik1VSXpNREEzTkRkRlF6SXlOVEJCUmpaRE5qYzVSRE0yUmpSRlFqWXhSRVUxTUVNek9VTkVPQSJ9.eyJodHRwOi8vc3RyYi11c2VyIjp7ImZpcnN0X25hbWUiOiJDaHJpcyIsImxhc3R123456789IlN0YW5sZXkifSwibmlja25hbWUiOiJjaHJpcyIsIm5hbWUiOiJjaHJpc0BjaHJpcy5pbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci8zYTE1ZmRmNmYzMjQ0YTNhNzJjZjAwMTM2NTNlMjA2ZD9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRmNoLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE5LTA2LTIxVDE4OjEyOjQ1LjExM1oiLCJlbWFpbCI6ImNocmlzQGNocmlzLmltIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJpc3MiOiJodHRwczovL3N0b3JiaWVzLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1YzU1ZmRhY2EyY2E4YjdkNzQwOTYwNTYiLCJhdWQiOiI1RmxCWlFUa0YwMHlGblhRMUZGaVhucFJKTUlGYjQ2aCIsImlhdCI6MTU2MTE0MDc2NSwiZXhwIjoxNTYxMTc2NzY1LCJhdF9oYXNoIjoibkpzUmZWUjVFS0ZwZE11MWFtZGY2dyIsIm5vbmNlIjoiSjctak1PZm1YNVZFbHhHZGJDcFh1TmdHY0JtSkVtTjMifQ.lPGptBsfPdn-PP-6yzE6aJGrnTf0dmfvRnUvdYVTvCwOo02Xn6pFcEahjKodIZ88FCwKvlQqfga0IbzDW2Ileq0q5xKfg-kTu5Ncy2MnJApjEIBeIQaUCMHbbj8HepYPftAkJdOmBhiQgsuBNCmix_HVM9fbvUNEdceARzEO1l1XyOP47KH0DYeMSHknWtGGQFRYUEYN-XJ123456789eA55IqOVRlAzeUNgqe-niWi8gaRAn0lKgASYvPdr1QNeWKEPJFNRkPGB0jtYJ2cF5lGLx-Yx2GyOtCucevMkennXHXfZauanQ2jUpeATVKm7DA69uoQB60NrR13I5INyxQ

Seriously, what in the holy crap is all that?! I don’t need that. All that information is safely stored in local storage. I purposely wrote my application to have shorter urls, so I absolutely cannot stand this unnecessary garbage being added. Please tell me how to stop this.

Now, that’s just an annoying problem. Here’s the horrific problem that WON’T ALLOW MY USERS TO LOG IN. Every time someone tries to log in, an alert pops up with “Invalid Token. See Console for details.” Checking the console, it says “state does not match.” Wtf is that? Just log in the user. I don’t give a ■■■■ about state. Just log in. That’s it. I’m about to end usage of this God damned service, because it has been nothing but ridiculous issues.

Hi @diaewad,

Sorry to hear your frustrations.

It looks like there is an access token, id token, and some other sensitive information in the URL. Would you be able to post or DM me a code snippet of the auth0 implementation in your app?

Also, it is important not to store sensitive information in local storage.

Thanks,
Dan

For the record, I had edited the url I posted, replacing much of the data with just “123456789,” so your edit wasn’t necessary. I wanted to keep the length to show how absolutely absurd the url was. Your edit does not exemplify the absurdity I wanted to showcase.

I am using this in an Angular app, and I have a navigation component with this code:

    if (!this.auth.isAuthenticated() && this.location.path() !== '/home') {
  if (!this.auth.handleAuthentication()) return this.router.navigate(['/home'])
}

In my Auth Service, I have this:

  public handleAuthentication(): boolean {
const setUser = (user: AuthProfileModel) => {
  this.userService.setUser({
    username: user.nickname,
    email: user.email,
    user_id: user.sub
  })
}

return this.auth0.parseHash((err: ErrorEvent, authResult: AuthResultModel) => {
  if (authResult && authResult.accessToken && authResult.idToken) {
    this.setSession(authResult)

    this.auth0.client.userInfo(authResult.accessToken, (err: ErrorEvent, user: AuthProfileModel) => {
        setUser(user)
      })
    return true
  } else if (err) {
    this.router.navigate(['/home'])
    console.error(err)
    alert(`Error: ${err.error}. Check the console for further details.`)
    return false
  } else if (!authResult && this.isAuthenticated()) {
    this.auth0.client.userInfo(getItem('access_token'), (err: ErrorEvent, user: AuthProfileModel) => {
      setUser(user)
    })
    return true
  } else if (this.isAuthenticated()) {
    return true
  }
})

}

It seems to me that the problem is the this.auth0.parseHash function is “slow,” and by the time it finishes what it’s doing (setting the user and tokens), the app itself has already navigated to the appropriate page. However, this.auth0.parseHash does not seem to be a Promise or Stream, so I can’t actively wait for it to complete before navigating.

@diaewad,

I reverted it to your original post so your point isn’t lost.

The parseHash function is making an external call during the this.auth0.client.userInfo function. This explains the ‘wait’.

Have you correctly set your redirectUri? It seems like the problem with the URL is coming from wherever that /main redirect is happening, not the /home route. It is possible that is also the source of your state error, especially if that route is happening twice like this example.

Could you post the code associated with that route?

Thanks,
Dan

PS Here is the AuthService we provide in our quickstart for reference.

Yes, I correctly set my callback uri. The problem is that I don’t want that uri to be accessible unless someone is logged in, and thanks to the wait period of the external call of this.auth0.client.userInfo that does NOT return a Promise or Stream, when auth0 routes to the callback url, it hasn’t put the expires_at key into local storage yet, so this.authService.isAuthenticated returns false, and my nav component routes right back to /home. This is actually why I opted not to use an AuthGuard via CanActivate on my Angular app, because I had to create all kinds of Behavior Subjects and Streams to wait and see if that token ever gets set, and to see if the UserService was setting a user, but if someone manually routed to a non-public url, those things would never return a value, because nothing was setting them, and relying on a timeout is just hacky.

Can you try to separate the userInfo request from the handleAuthentication function all together? Then you can call a getProfile method in a profile component. Something like this


AuthService:

  public handleAuthentication(): void {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.localLogin(authResult);
        this.router.navigate(['/home']);
      } else if (err) {
        this.router.navigate(['/home']);
        console.log(err);
        alert(`Error: ${err.error}. Check the console for further details.`);
      }
    });
  }

  private localLogin(authResult): void {
    // Set the time that the access token will expire at
    const expiresAt = (authResult.expiresIn * 1000) + Date.now();
    this._accessToken = authResult.accessToken;
    this._idToken = authResult.idToken;
    this._expiresAt = expiresAt;
  }

  public getProfile(cb): void {
    if (!this._accessToken) {
      throw new Error('Access token must exist to fetch profile');
    }

    const self = this;
    this.auth0.client.userInfo(this._accessToken, (err, profile) => {
      if (profile) {
        self.userProfile = profile;
      }
      cb(err, profile);
    });
  }

Again, the problem lies in the race condition. When do I get the user profile? When the redirect happens, the token still isn’t set, so using the getProfile function will just throw the error. What needs to happen is that parseHash function needs to be a Promise or a Stream, so it can either be watched or subscribed to. Then once the authResult comes through, the rest of the app can verify the validation. In my version, I have it returning a boolean in the hopes that would somehow help, but then there’s the state issue. I’ve tried returning it as an Observable (return of(this.auth0.parseHash
)), and it still doesn’t work.

Do you need the profile to load directly after login? How it is handled in the example app I linked above, the user has to visit a /profile route before that call is made. In essence, the user logs in, is redirected to a home page, then will have to navigate to a profile route before the getProfile method is called. This would be more efficient as well, since the /userinfo endpoint is not called on every authentication.

We also just released the new auth0-spa-js with auth code + pkce (the recent standard grant type for SPAs according to the OAuth2.0 Spec). It looks like they may have addressed your issue. Take a look at the new example here.

Thanks,
Dan

Hi @diaewad,

Sorry you’re having issues. Just to jump in here, take a look at our Angular aside code that we use in articles. It is up-to-date with best practices for the auth0.js SDK you’re using. It also demonstrates how to convert the Node calls in the SDK to observables to make them easier to work with in Angular.

For example, the functions for handling the callback (which would be called on your callback component load) look like this:

 handleLoginCallback() {
    if (window.location.hash && !this.isAuthenticated) {
      this.parseHash$().subscribe(
        authResult => {
          this.localLogin(authResult);
          this.router.navigate([this.onAuthSuccessUrl]);
        },
        err => this.handleError(err)
      )
    }
  }

  private localLogin(authResult) {
    // Observable of token
    this.token$.next(authResult.accessToken);
    // Emit value for user data subject
    this.userProfile$.next(authResult.idTokenPayload);
    // Set flag in local storage stating this app is logged in
    localStorage.setItem(this.authFlag, JSON.stringify(true));
  }

Hopefully the sample code in that repo I mentioned will help clear up your issues.

As @dan.woda mentioned, though, the new SPA SDK that was just released is lightweight and uses a different auth flow, so it might solve your issues as well. We have a brand new Angular QuickStart that uses this new SDK. Check it out and see if it solves your problem.

Hope that helps.

Cheers,
Sam Julien

Omg this is amazing! Thank you so much! I wish I had known this existed! I have some refactoring to do, but this is beautiful!

2 Likes

Fantastic! :tada: Let us know if you need any further help!

1 Like

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.