React SPA cannot refresh user data after update

I’m developing a SPA web app at the moment with the React SPA SDK; I access user’s profile information through through the useAuth0 hook.

When the user wants to update their profile (name etc), that information is posted to my backend, where I use the .net management SDK to complete the update. So far, so good.

After the successful update, I need a way to tell the React SDK to refresh the user information from Auth0 - at the moment only logging out/back in will refresh the data coming from the useAuth0 hook.

I’ve found older threads which say to call getAccessTokenSilently({ ignoreCache: true }); I’ve tried this (updated to use cacheMode: 'off') but it doesn’t seem to make a difference. To be clear; I don’t want the user to have to do anything at all; I just want the hook to return updated information.

Is there some way to force the update?

Hey there @jono.rogers welcome to the community!

Using getAccessTokenSilently should indeed also update the user:

I just tested in the React sample app and do see the user’s profile information updated (I’m changing the name property) in Profile.js which uses useAuth0. For testing, I’m just relying on the call to getAccessTokenSilently in ExternalAPI.js.

When calling getAccessTokenSilently in your own code, do see a successful silent auth event in your dashboard logs (Monitoring → Logs)? If you are using refresh tokens it will show up as a successful refresh token exchange.

Hi Ty,

Thanks for the reply… No; I don’t see a successful silent auth event in my Auth0 dashboard logs. I do see the succesful user update API operation, but that’s it. I log (to the JS console) the result of the getAccessTokenSilently call; and that returns id and access tokens (detailedResponse is turned on), but that id_token does not have the updated information… I also don’t see any network call to auth0 in dev tools; so I don’t see how it could be updating it…

Here’s my provider:

<Auth0Provider
			domain={domain}
			clientId={clientId}
			authorizationParams={{
				redirect_uri: redirectUri,
				audience: audience,
			}}
			useRefreshTokens={true}
			cacheLocation="localstorage"
			onRedirectCallback={onRedirectCallback}
		>
			{children}
		</Auth0Provider>

And after a successful save (after my backend has indicated it’s successfully updated the management API)

const token = await getAccessTokenSilently({
					cacheMode: "off",
					detailedResponse: true,
				});

I’m not doing anything with the returned token, it’s just there for temporary logging…

Ok, so I’ve made a bit of progress here; and I have a workaround, but I’d still appreciate some answers.

No 1; thanks to some more digging I found I needed to enable Offline Access on my API settings to enable the refresh tokens properly…

No 2; I updated the scopes in AuthProvider - I’m still not sure if this is necessary, new code below:

<Auth0Provider
	domain={domain}
	clientId={clientId}
	authorizationParams={{
		redirect_uri: redirectUri,
		audience: audience,
		scope: "openid profile email offline_access",
	}}
	useRefreshTokens={true}
	cacheLocation="localstorage"
	onRedirectCallback={onRedirectCallback}
>
	{children}
</Auth0Provider>

No 3; (and most importantly), I have changed to calling getAccessTokenSilently through a useEffect rather than synchronously after my API update call in an event handler. This was my previous update code:

const onSave = async (updatedProfile: ProfileFormData) => {
	if (canSave) {
		try {
            // API Update (using management API)
			await updateUser(updatedUser).unwrap();
                         
            await getAccessTokenSilently({ cacheMode: 'off' });
		} catch (err) {
			// Error logging
		}
	}
};

Now, I use this effect:

useEffect(() => {
	const loadToken = async () => {
		await getAccessTokenSilently({ cacheMode: "off" });
	};
	if (updateSuccessful) {
		loadToken();
	}
}, [updateSuccessful]);

Luckily I have some state from my API library that I can use to fire this effect once after a successful API response; but I don’t see why I would have to - what’s the difference?

I arrived at this fix because I thought I would try and put the getAccessTokenSilently call into a useEffect on page load, and it immediately threw errors related to refresh tokens (which led me to fixes 1 & 2), but the call in the event handler doesn’t do that at all. Am I missing something about javascript/react, or is this a bug in the Auth0 SDK?

1 Like

Thanks for following up!

Good catch! This is indeed required.

If using useRefreshTokens={true} you do not need to add the offline_access scope, the SDK adds this for you.

So are you using/setting updateSuccessful in your onSave function now for useEffect to listen to? While not a React expert, I believe useEffect better aligns with React’s state and lifecycle management.

I’m not doing it myself - it’s a peice of data from RTK Query, which I’m using; it gets set after a successful API call (in this case, updating the user):

const [
	updateUser,
	{ isLoading: isUpdating, isSuccess: updateSuccessful },
] = useUpdateUserMutation();

I’m not really sure what you mean by that… In my code, I have an event handler which runs when the user presses a button (onSave, from my previous post). That event handler is responsible for sending an API call to my backend to update; and handling any side-effects (like resetting the form). In this case, that involves somehow??? telling the Auth0 SDK to refresh the user information.

That should cause the useAuth0 hook to trigger a re-render with updated state - that’s how React is supposed to work.

In my case, I’m having to add a separate useEffect call, which should not be necessary, to update the Auth0 SDK - and this is the part that is weird. Why doesn’t the call to getAccessTokenSilently work in the event handler, the same way it does in the useEffect call? Through my testing, the two approaches are working completely differently…

I’ll try and see if I can illustrate the difference using the starter template…

Hi Ty,

I’ve forked the starter template you linked, and I’m looking to update the username like you said you did - but from what I can see, that would mean me adding the management API SDK to some sort of backend - how did you do that step? Did you use the node.js library on the api-server, or something different?

Or did you just update the name property on the user object returned from useAuth0?

1 Like

Thanks for the further information :slight_smile:

I just updated the user’s name in the dashboard - Alternatively (and more realistically) yes this would be handled by a backend proxy of sorts. Here’s a simple example of that:

Right, that makes sense.

Ok; testing in the sample app, I cannot recreate what happens on my web app - but I have found some interesting things.

This is my event handler which is causing issues, for reference:

const onSave = async (updatedProfile: ProfileFormData) => {
    // Do some processing of my form, then send network call
	await updateUser(request).unwrap();
    // Now update the user
    await getAccessTokenSilently({ cacheMode: 'off' });
};

This doesn’t work; however this does:

const refreshToken = async () => {
	await getAccessTokenSilently({ cacheMode: "off" });
};

const onSave = async (updatedProfile: ProfileFormData) => {
    // Do some processing of my form, then send network call
	await updateUser(request).unwrap();
    // Now update the user
	setTimeout(refreshToken, 0);
};

So moving the call from the job queue (running after awaiting a promise) to the event queue (like how setTimeout is implemented) fixes the issue.

This also works (confirmed by changing the username on the dashboard before trying, just swapping the order of the calls:

const onSave = async (updatedProfile: ProfileFormData) => {
    // Now update the user
    await getAccessTokenSilently({ cacheMode: 'off' });
    // Do some processing of my form, then send network call
	await updateUser(request).unwrap();
};

I tried to replicate this issue in the sample app, but I was unsuccessful; the below code works fine (which I think, should have the same issue?):

const handleRefresh = async () => {
	const fact = await fetch("https://catfact.ninja/fact");
	console.log(fact);
	await getAccessTokenSilently({ cacheMode: "off" });
};

So I don’t know - maybe the RTK query mutation promise that I’m awaiting is doing something differently under the hood which affects things? I’m not enough of an expert in JS internals to know.

Anyway, for now I’m running with the setTimeout workaround; it seems the less hacky approach

Hey @jono.rogers thanks for following up!

Hmm, that could be possible - I haven’t been able to reproduce what you’re seeing either so perhaps it does have something to do with it. I am unfortunately in the same boat in not being an expert in JS internals - You could always open up an issue against the SDK itself. The maintainers of the SDK may be able to shed some more light on it.

Thanks for all your help @tyf