Hi,
I've followed the advice in https://github.com/auth0/auth0-spa-js/issues/449 to allow us to handle when the refresh_token has expired. I manually create the client and then call checkSession, instead of using createAuth0Client. I'm using rotating refresh tokens and local storage.
If checkSession fails, I call .logout({localOnly: true}) to clear the token from local storage (otherwise I end up getting errors after re-login) and then immediately call loginWithRedirect.
The problem that I'm running into is that sometimes transaction cookies (a0.spajs.txs...) are being left behind. The cookies are all sent with the requests and it's possible to end up with enough cookies that I start receiving 431 Request Header Fields Too Large errors which I can only resolve by clearing cookies.
With a long refresh_token expiry time, this is unlikely to become a problem (I'm testing with a very short token lifetime at the moment) but I'd like to make sure my implementation is cleaning up after itself.
Is there any way to clear out the old cookies? Is the fact that this can happen a bug in library or should I be handling it myself?
Thanks
Thanks for raising. The transaction cookies should be cleared by completing the login flow, by calling handleRedirectCallback when control returns from Auth0 Server after login. Can you confirm that this call is taking place?
So the flow should be:
loginWithRedirecthandleRedirectCallbackThanks for the super quick response! Sorry for the slow reply... i got dragged on to making fixes for a release....
I've dug a little deeper and I think the problem was that loginWithRedirect was potentially getting called in two places (once in the catch block when checkSession errored because of the expired refresh token) and once in the guard. This obviously caused the client to create two cookies but only one was being cleared by the callback.
I had imagined login would synchronously update the browser's location (so that there wouldn't be a risk of more code executing) but I looked at the code and I see there's a promise when building the authorize url.
I've updated so that when I get an error in client creation I only perform the local logout to clear the refresh token and loginWithRedirect doesn't get called until the guard runs. Does that seem a reasonable solution? I'm not totally sure what the expected flow is. My initial tests seem to work ok but I need to test the different use cases/browsers a bit deeper.
The expected flow is:
createAuth0Client with your domain and client_id values, as well as a redirect_uri parameter - this is where Auth0 will redirect _back_ to after log inloginWithRedirect when you need to log the user in. This will create the transaction cookie and redirect the browser away to the authorization server.handleRedirectCallback is called somewhere on that flow. This will process the transaction cookie and delete itThe last step can be handled in a couple of ways. Either:
code and state parameters, and handle it then (example)http://localhost:3000/callback, for example) then redirect elsewhere.Closing this for now but feel free to continue if you need further help.
Thanks... That's essentially what I'm doing :)
The problem for me is that I couldn't use createAuth0Client because I'm using refresh tokens and it throws an error if the refresh token is expired. I use the constructor directly so that I still have the client if checkSession fails. Login fails if you are using local storage and it contains an expired refresh token because it tries to send it again and gets an error... so it's necessary to clear the local storage with logout. The thing that isn't so well documented is the flow for when you get an invalid_refreshtoken error.
I'm not really sure if the existing behaviour is a bug or a missing feature or the intended functionality.
Glad we're on the same page! Let me know which version of the SDK you're using; we made some changes in 1.9.0 to specifically make sure you get the client instance back even in the event of an error.
I'm on 1.9.0. I'm creating the client like this:
let authClient: Auth0Client;
return from(
(async () => {
// this is the same as the code from createAuth0Client but allows us to handle any errors thrown up by
// checkSession (happens when the refresh_token has expired
authClient = new Auth0Client(clientOptions);
await authClient.checkSession();
return authClient;
})()
).pipe(
catchError(err => {
console.error(err);
// there is currently a limitation in Auth0 client - if an expired refresh token is store in local storage it errors and doesn't
// cleanly clear the local storage so we have to do it manually. see https://github.com/auth0/auth0-spa-js/issues/449
authClient.logout({
localOnly: true
});
return of(authClient);
}),
shareReplay(1) // Every sub receives the same shared value
);
Thanks. The only thing you can do when you get an invalid_refreshtoken error is to ensure that you put the user through the interactive login flow again to get a new token. Looks like you're doing that by logging them out, but we could be doing a better job of cleaning that up ourselves when we throw that error. Let me put something in our backlog to fix that.
Yes, the logout makes sure that the local storage is clean and doesn't keep sending the expired refresh token (I think that might be a bug). Then the guard or interceptor handle forcing the login again.
Thanks for the help/explanation :)
Most helpful comment
Thanks. The only thing you can do when you get an
invalid_refreshtokenerror is to ensure that you put the user through the interactive login flow again to get a new token. Looks like you're doing that by logging them out, but we could be doing a better job of cleaning that up ourselves when we throw that error. Let me put something in our backlog to fix that.