Looking for recommended pattern to avoid "No matching state found in storage"
Issue
Our users face "No matching state found in storage" once in a while, in fact, often enough to be a concern.
Hopefully this is due to misconfiguration on our end, and if so this issue could be helpful for others too.
Context
We use v3.3.0 with oidc-client-ts v.3.2.0, in a Vite/React 18/React Router (declarative) app.
In the happy path, we have a route guard implementing a version of useAutoSignin (I also tried the library implementation, and it seems to have the same issues). Our implementation looks like this, based on an older version of the docs:
// automatically sign-in
useEffect(() => {
if (
!hasAuthParams() &&
!isAuthenticated &&
!activeNavigator &&
!isLoading
) {
signinRedirect({
state: window.location.pathname,
});
}
}, [isAuthenticated, activeNavigator, isLoading, signinRedirect]);
Following this flow, we're not sure if users ever face issues. At least we cannot reproduce them.
Reproduction
Instead, to reproduce the issue, I can go about it in 2 ways:
- Manually navigate to the post-redirect-destination URL, e.g. http://localhost:3000/login?code=1ABAC1819E0D7116424252F8F6F51983110FB73323C9912E57A34B51B467F4CA-1&scope=openid&state=16de9e797d504527804387146331b7d3&session_state=czrJc-zJU6oCpvt1oYawiSnLr7KqTAAZZ9oDPLatoew.B4AB755299D837CF49533F634CF83600&iss=https%3A%2F%2Fauth.example.com
- or navigate backwards in the browser history, leaving the client app and ending up on the issuer's login page again
What underlying issues are caused by the reproduction
Both flows bypass the part where signinRedirect sets state in local storage, thus the mechanisms in place to handle the login fail, and the user is faced with "No matching state found in storage".
Reproduction = real issue?
Again, I'm not sure if there are other ways our users are finding themselves in this place, but if these 2 cases can be mitigated, we would soon be informed if the issue still persists despite these reproductions no longer being possible.
Question
So, finally, concrete question: is there any reasonable way to guard against my 2 reproduciton paths?
Sidenotes
We do implement onSigninCallback in the recommended way.
Also, I did try to do a naive workaround like:
useEffect(() => {
if (error?.message === "No matching state found in storage") {
reLoginTried.current = true;
signinRedirect();
} else {
reLoginTried.current = false;
}
}, [error?.message, signinRedirect]);
it actually worked about 50% of the time, but the other 50% it would cause various Network/Operation aborted errors.
I do not have a solid suggestion, but adding to the conversation that this manifested for a group of our users because they saved a URL with the code and state parameters as a bookmark and shared that with other users. This is probably unlikely to be a real issue in a proper implementation; we had an edge case where those code and state parameters were not being cleared out and I'm supposing that was the root of our users problem. We have since fixed that edge case and informed our affected users on how to proceed.
But in terms of solutions; using a similar check to your "naive workaround" but instead of performing the sign in we clear out the code and state query parameters from our route and let our existing state machine catch and fire off the sign in request. That seems to be handling this case in a consistent way. But still work shopping with our engineering team if we are satisfied with that.