Skip to content

Auto login and nonce/state error question #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
paqogomez opened this issue Jan 19, 2018 · 26 comments
Closed

Auto login and nonce/state error question #218

paqogomez opened this issue Jan 19, 2018 · 26 comments
Labels
more-info-needed Please provide a minimal example (e.g. at stackblitz.com) which demonstrates the issue

Comments

@paqogomez
Copy link

paqogomez commented Jan 19, 2018

I just upgraded to version 3.1 and i'm getting an error.

I'm not sure if this is applicable, but my site is configured to hit the domain, realize its insecure, then redirect to the login. (auto login)

In app.component.ts I've got this:

    let self = this;
    this.oauthService.configure(authConfig);
    this.oauthService.setStorage(localStorage);
    this.oauthService.tokenValidationHandler = new JwksValidationHandler();
    this.oauthService.loadDiscoveryDocumentAndLogin().then((res) => {
        self.oauthService.setupAutomaticSilentRefresh();
    });

So, my request hits the domain name, heads off to identity server and comes back with the id_token hash then throws this error:

validating access_token failed. wrong state/nonce. null yey1KhuQ9ewceuY7Qpbic4sWg5UAt8BJ6YYNryuY

When i debugged through this, I noticed that the nonce was saved, then on the second trip, the nonce wasnt in the local storage any more. So the comparison is between a valid nonce and null.

I tried creating login options with disableOAuth2StateCheck, but that just kicked the can down the road a bit. I got the error

Error validating tokens Wrong nonce: 3BAgeBU3kWkX5EiDKFDaGThnWj6pYqmsAXpf2HSO 

Here is the code I have now:

    this.oauthService.configure(authConfig);
    this.oauthService.setStorage(localStorage);
    this.oauthService.tokenValidationHandler = new JwksValidationHandler();
    let loginOptions = new LoginOptions();
    loginOptions.disableOAuth2StateCheck = true;
    this.oauthService.loadDiscoveryDocumentAndLogin(loginOptions).then((res) => {
        if (res)
            self.oauthService.setupAutomaticSilentRefresh();
    });

EDIT:
I was able to work around this error by using the disable state check as shown above and commenting out the other state check in node_modules/angular-oauth2-oidc/angular-oauth2-oidc.umd.js. Lines 1702 - 1706.

What would the fix be?

@paqogomez paqogomez changed the title Auto login and nonce/state error Auto login and nonce/state error question Jan 19, 2018
@vchacon
Copy link

vchacon commented Jan 25, 2018

I am having the same error and behavior but while using trylogin/initImplicitflow calls. I am also interested in what the fix would be.

Thanks

@stephanos199
Copy link

stephanos199 commented Feb 2, 2018

I have the same error but cannot reproduce it consistently. I tried creating a custom storage to log every read, write and remove from the localStorage but i cannot seem to find the exact moment when the nonce is removed on the second round trip to the login page. Most of the time everything works fine, then some times it fails with an invalid nonce in state error.

@vchacon
Copy link

vchacon commented Feb 8, 2018

Resolution of this issue, at least for us. It turns out that our server had the redirects deleted. When people used the HTTP address they were not redirected to the HTTPS address before starting the authentication process. The authentication process would start in HTTP and save the NONCE in the corresponding local/session storage. The redirect url parameter used was always HTTPS so upon authentication the user was redirected to the HTTPS address and would not find the NONCE in storage.

Once we corrected the server redirect to HTTPS://www.somesite.com/ our NONCE issue disappears as we found it in local storage.

@jeremykirkup
Copy link

I have the same issue attempting to use tryLogin(). In my case the auth server does not return an id_token in the redirect url, just access_token and expires_in query string parameters (its an older implementation that does not support OIDC and all the login params need to be passed to the login URL. So in my case there would be no nonce. is there a way that the library can be configured to handle this scenario?

Thanks

@karlmnz
Copy link

karlmnz commented Mar 22, 2018

Same issue here, v3.1.4. and I'm not sure why I am not get my nonce and token into session storage when going through login page/redirect.

Using

oauthService.loadDiscoveryDocumentAndTryLogin()
            .then(result => {
                if (!oauthService.hasValidAccessToken()) {
                    oauthService.initImplicitFlow();
                }
            });

        oauthService.events.filter(e => e.type === "token_received").subscribe(e => {
            if (oauthService.hasValidAccessToken())
                oauthService.loadUserProfile().then(userProfile => {
                    this.userProfileLoadedEvent.emit(userProfile);
                });
        });

Can replicate everytime in following 2 cases:

  1. 'ng serve' on dev localhost, browse to localhost:4200 and goes to cognito login page happily and redirects back, but doesn't load my app as doesn't think it gets valid token ...

In this case in chrome, edge, FF, opera, they all happily login and load app straight away.

But if ...

  1. 'ng build --prod --environment dev', then run on my localhost with live-server. Now in chrome I get the same behaviour as IE above. And then console shows error:

'validating access_token failed. wrong state/nonce. null'

nonce and token are not written to sessionStorage.

The url in chrome from redirect is:

http://localhost:4200/#access_token=eyJraWQiOiJjeUZt...nbw&state=D4LRaiSiQSob9wJKpiCpa1ps9GmZwMHsbPuOXG70&token_type=Bearer&expires_in=3600

My stays in the loading state as doesn't think has valid token which is right as not in session. I then make the browser address bar url http://localhost:4200 and hit enter and it again goes passed cognito token validation and loads my app straight away getting nonce and token in session.

If I change browser address url to http://localhost:4200/# instead and hit enter when loading failed, it stays in the loading state cause the nonce and token is missing from the session.

Any ideas? For me not using https. and redirect is not https.

@karlmnz
Copy link

karlmnz commented Mar 22, 2018

Decided to have a quick look at catching the code (I didn't original write this on my app) and this is what I found, change to include catch and handle err locally:

oauthService.loadDiscoveryDocumentAndTryLogin()
            .catch(err => {console.error(err);})
            .then(result => {
                if (!oauthService.hasValidAccessToken()) {
                    oauthService.initImplicitFlow();
                }
            });

And it skips my global error handler and no longer gets stuck loading. So found problem have to now figure out how to or if need to handle error ...

@megdawald
Copy link

I'm having the same issue with tryLogin() and using initImplicitFlow(). I get an uncaught in promise error when calling tryLogin().
I also get the "validating access_token failed. wrong state/nonce" error.
Do previous versions work when trying to call those 2 functions or has anyone found a fix for this issue? Thanks in advance!

@raviverel
Copy link

any news?

@gridpatrik
Copy link

Only seeing this in IE not in Chrome.

@raviverel
Copy link

it happen to me at chrome

@gridpatrik
Copy link

Did a quick test and changed storage to local storage instead of session storage.

this.oauthService.setStorage(localStorage);

This seems to solve the problem in IE indicating that there is something related to sessionStorage in IE that is my issue.

@raviverel
Copy link

look like it solve my problem also :)
any security issue switch to local storage ?

@gridpatrik
Copy link

The lifetime of sessionStorage is normally shorter than localStorage and might therefore be considered the better option of the two. However, the lifetime of your token might invalidate that argument.
https://www.w3.org/TR/webstorage/#the-sessionstorage-attribute
https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage

@Brototype
Copy link

using the localStorage in combination with using the built in interceptor will not work as of issue #255

@manfredsteyer
Copy link
Owner

I've seen sth like this before. It was a race condition. Can you please provide an minimal stackblitz.com example for this?

@manfredsteyer manfredsteyer added more-info-needed Please provide a minimal example (e.g. at stackblitz.com) which demonstrates the issue support request/ not a bug labels May 11, 2018
@NadinePe NadinePe mentioned this issue May 30, 2018
@bernard4uece
Copy link

I am using this library and silent refreshes are giving me the error as validating access_token failed. wrong state/nonce. I have set up oidc to use local storage. Any help would be great.

@dylandechant
Copy link

here is an example we used to get around this by manually calling silent refresh:

Observable.interval(this.SILENT_REFRESH_INTERVAL)
      .subscribe(s => {
        this.oauthService
          .silentRefresh()
          .then(info => {
            console.log('success refresh'
          })
          .catch(err => console.error('token refresh error', err));
      });

hope it helps anyone

@anupavanm
Copy link

Same Issue for me in chrome

Tried switching to localstorage and it worked as @gridpatrik mentioned ! Any work around to make it work with sessionstorage.

@dylandechant i tried your approach and does not seem to work.

@manfredsteyer Any update ?

jeroenheijmans added a commit to jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards that referenced this issue Aug 19, 2018
See: manfredsteyer/angular-oauth2-oidc#218

To reproduce:

1. Run `ng serve` for this commit
2. Open multiple (e.g. 4) Chrome windows with the demo app
3. Open the devtools Console on all of them
4. Keep the code open in VSCode
5. Log in on one window
6. Save any file to cause `ng` to reload all windows
7. Click "Clear Storage" button
8. Save any file to cause `ng` to reload all windows

Rinse and repeat step 7/8 until the issue arises, clearly visible in
the dev tools console.
@jeroenheijmans
Copy link
Collaborator

We were running into the same issue, and I think I've tracked down a repro. It's quite reliable on my machine, but it does seem to be a timing issue or race condition, so the repro doesn't always immediately work.

Repro

To reproduce:

  1. Run ng serve for this branch in my sample repo
  2. Open multiple (e.g. 4) Chrome windows with the demo app
  3. Open the devtools Console on all of them
  4. Keep the code open in VSCode
  5. Log in on one window
  6. Save any file to cause ng to reload all windows
  7. Click "Clear Storage" button
  8. Save any file to cause ng to reload all windows

Rinse and repeat step 7/8 until the issue arises, clearly visible in the dev tools console.

  • Result: 3 out of 4 windows encounter invalid_nonce_in_state, 1 of them runs just fine
  • Expected: all 4 windows to run just fine

Truth be told, this is a very artificial repro, but it is the most reliable one I could make. Various posters above also mention the issue, and I myself encountered a hard-to-repro version of this issue when users close Chrome, reopen it, and try to restore multiple tabs at once. I presume my artificial repro resembles the real problem well enough.

Debug info

To create useful debug info, I am logging all OAuthEvent events and also console.log(...)ing all StorageEvents where the key is 'nonce'.

Here's a screenshot of a window that went bad, and the one that loaded just fine, side by side (other 2 windows that went bad not shown):

image

Here's a rather large animated gif that shows this in full effect (when my mouse goes off-screen I go to VSCode to save a file and trigger ng's hot-reloading):

repro-for-invalid-nonce-in-state-error

Hypothesis

I speculate that the issue arises because the nonce state check will validateNonceForAccessToken against the this._storage.getItem('nonce') which might've been changed in the mean time by another window/tab in the same browser.

I don't think the browser or type of storage are the root cause of this issue. It is quite possible though that they affect the issue, as they might have different kinds of multi-threading, e.g. deciding to give larger chunks of CPU time to one tab at a time, hiding the actual issue.

Solution

Not really a clue. Perhaps the this._storage.getItem('nonce') call should be done earlier on and passed to the validateNonceForAccessToken(...) call instead?

Workarounds

In my sample repository, the master branch does have the errors, but that doesn't seem to hurt too much, because:

But then again, it's merely a sample repository, not really battle-tested. But perhaps it helps someone.

For what it's worth, here's a shortened version of my current login flow:

this.oauthService.loadDiscoveryDocument()
  .then(() => this.oauthService.tryLogin())
  .then(() => {
    if (this.oauthService.hasValidAccessToken()) { return Promise.resolve(); }
    return this.oauthService.silentRefresh()
      .then(() => Promise.resolve())
      .catch(result => {
        const errorResponsesRequiringUserInteraction = [
          'interaction_required',
          'login_required',
          'account_selection_required',
          'consent_required',
        ];

        if (result && result.reason && errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {
          console.warn('User interaction is needed to log in, we will wait for the user to manually log in.');
          // Could also call this.oauthService.initImplicitFlow() here...
          return Promise.resolve();
        }

        return Promise.reject(result);
      });
  })
  .then(() => this.isDoneLoadingSubject$.next(true))
  .catch(() => this.isDoneLoadingSubject$.next(true));
window.addEventListener('storage', (event) => {
  if (event.key === 'access_token' || event.key === null) {
    console.warn('Noticed changes to access_token (most likely from another tab), updating isAuthenticated');
    this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
  }
});

this.oauthService.events
  .subscribe(_ => {
    this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
  });

this.oauthService.events
  .pipe(filter(e => ['token_received'].includes(e.type)))
  .subscribe(e => this.oauthService.loadUserProfile());

this.oauthService.events
  .pipe(filter(e => ['session_terminated', 'session_error'].includes(e.type)))
  .subscribe(e => this.navigateToLoginPage());

this.oauthService.setupAutomaticSilentRefresh();
private isAuthenticatedSubject$ = new BehaviorSubject<boolean>(false);
public isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();

private isDoneLoadingSubject$ = new ReplaySubject<boolean>();
public isDoneLoading$ = this.isDoneLoadingSubject$.asObservable();

public canActivateProtectedRoutes$: Observable<boolean> = combineLatest(
  this.isAuthenticated$,
  this.isDoneLoading$
).pipe(map(values => values.every(b => b)));

@ronnyek
Copy link

ronnyek commented Nov 27, 2018

was this actually resolved? I feel like I see these errors quite frequently... except for me there doesnt seem to be any rhyme or reason to it.

@jeroenheijmans
Copy link
Collaborator

@ronnyek The issue was (I think) closed at the time because there was no reliable repro. The only repro after that AFAICT is my post above yours, which also contains the resolution I currently use.

If you still have errors, I suggest reading through my comment (I know, wall of text, but hey...) and trying out the workaround there. If you still experience issues after that, try to make a minimal reproducible example (e.g. on StackBlitz) and open a new issue for it, referencing this one.

@RagnarokLi
Copy link

This seems to be Edge issue. See here from more info,

https://github.com/AzureAD/azure-activedirectory-library-for-js/wiki/Known-issues-on-Edge

@SalkiDixit
Copy link

@dylandechant Hi, I am stuck while implementing silent-refresh, I am not able to do this.
Would you please assist silent-refresh.

@ChristophWieske
Copy link

ChristophWieske commented Nov 15, 2019

HTTP vs. HTTPS

I had this issue as well and was able to solve it.

The problem I had was that the frontend accepted http-requests the redirect on the other side pushed the user to the https-adress. So initImplicitFlow pushed the nonce (in my case) to the local storage but when it came back the local storage was empty (different url).

Changed the frontend to run https only. Everything is fine now.

Check that out. It may be the cause for some of you.

@ChristophWieske
Copy link

@manfredsteyer maybe a good approach to output console warning when initImplicitFlow is called when location.host != redirectUri.host. That would leed to the issue behavior everytime, doesn´t it?

@InDieTasten
Copy link

For anyone coming across this, I just figured out I accidentally called setupAutomaticSilentRenew twice, which caused the race condition on the first of the two renewal requests to fail due to the mentioned 'race condition'.

And since I don't see any obvious use cases for calling it twice, maybe the second call to this function should be no-op? Or maybe throw 'already setup' error?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
more-info-needed Please provide a minimal example (e.g. at stackblitz.com) which demonstrates the issue
Projects
None yet
Development

No branches or pull requests