This issue can be reproduced for different frameworks like React as well. In other words, It isn’t specific to Angular.
In summary, before a login transaction starts auth0-spa-js library creates a random state variable and stores it in a cookie. Then, it makes a redirect to Auth0 server with this state parameter. After the user is authenticated Auth0 returns a code along with the same state parameter. Auth-spa-js library checks if the state returned matches with the state stored in the cookie and if it matches makes a code exchange call to get the issued tokens.
auth0-spa-js deletes the state cookie just before the code exchange request. If the user refreshes the browser before the code exchange completes, same code block in the library runs again but this time due to the state cookie being deleted, library throws a state mismatch error.
The state cookie deletion happens on this line. If transactionManager.remove() function call was moved anywhere after the token exchange API call, it could help to avoid the state mismatch error after the page refresh but this doesn’t help on its’ own. That is because, if the first oauth/token call hits Auth0 server, the code becomes invalid hence the next call with the same code fails with an invalid authorization code error. Unfortunately, as of today, invalidation of the code is mandatory by RFC6749 section 4.1.2 so it isn’t possible to allow code reuse at this time.
code
REQUIRED. The authorization code generated by the
authorization server. The authorization code MUST expire
shortly after it is issued to mitigate the risk of leaks. A
maximum authorization code lifetime of 10 minutes is
RECOMMENDED. The client MUST NOT use the authorization code
more than once. If an authorization code is used more than
once, the authorization server MUST deny the request and SHOULD
revoke (when possible) all tokens previously issued based on
that authorization code. The authorization code is bound to
the client identifier and redirection URI.
I would like to offer two workaround solutions to this issue.
1- Once the app detects an invalid state error, in the error handler it may start a new authentication request for the state mismatch errors. This will log the user in without the login page displayed if the seamless SSO feature is enabled on the tenant.
To prevent any infinite loops due to state mismatch error, app may create a new cookie to keep the state of the retry mechanism. This can ensure that the login attempts happen only for a few times.
2- Alternatively, you may implement logging in with a popup window as documented here. With the popup mode, users may have a better idea that the login step didn’t complete so they may not refresh the browser.