Reputation: 643
I'm working on a React app that uses auth0.js for user authentication.
Currently the process is:
Login
button on the homepage, which redirects them to the login page, where they enter login details and submit the form.handleAuthentication()
function is called, which parses the URL hash fragment and sets the user's session, which involves storing the access token, expiry time and user info in localStorage
to provide persistence on refresh.Login
button should be replaced with an avatar of the user's profile image and a link to their profile page.However, while this process works, it doesn't always work the first time round. Sometimes I need to refresh the page because the user info cannot be found. I've discovered the reason is that the call to auth0client.client.userInfo()
is asynchronous, so there is a slight delay in retrieving the user info to be stored in localStorage
. Therefore, it's likely that the homepage will render before Auth0 sends a response. By the time I refresh the page a second time, the user info has been retrieved and stored, so the homepage renders properly.
The following is some of the relevant code in the above process:
auth.login()
login(realm, username, password, handleIncorrect) {
// Attempt to log the user in
this.auth0Client.login(
{
realm: realm,
username: username,
password: password,
},
(err, authResult) => {
// handle result/error
}
);
}
auth.handleAuthentication()
handleAuthentication() {
// Attempt to authenticate the user in the app
this.auth0Client.parseHash({}, (err, authResult) => {
if (err) {
return;
} else if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
}
});
}
auth.setSession()
setSession(authResult) {
// Use the results from handleAuthentication() to set up the user's browser session
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime();
this.accessToken = authResult.accessToken;
this.idToken = authResult.idToken;
this.expiresAt = expiresAt;
localStorage.setItem("access_token", authResult.accessToken);
localStorage.setItem("id_token", authResult.idToken);
localStorage.setItem("expires_at", expiresAt);
this.auth0Client.client.userInfo(authResult.accessToken, (err, user) => {
if (err) {
console.log("setSession: Couldn't get user info");
return;
} else {
localStorage.setItem("user", JSON.stringify(user));
}
});
}
auth.getUserInfo()
getUserInfo() {
// Return user info
const user = localStorage.getItem("user");
if (!user) {
console.log("getUserInfo: No user info to retrieve");
return;
}
return JSON.parse(user);
}
App.js
export default function App() {
auth.handleAuthentication();
return (
<BrowserRouter>
<Suspense fallback={<p>Loading...</p>}>
<Layout>
<Switch>
// Routes to pages
</Switch>
</Layout>
</Suspense>
</BrowserRouter>
auth
is a custom class I've created to extend the default Auth0 functionality by using localStorage
, amongst other things.
App.js
shows that handleAuthentication()
(and by extension, setSession()
) is called just before rendering the new route (homepage in this case). But setSession()
often takes longer to complete than the page takes to render. So when the homepage loads and calls auth.getUserInfo()
, the user info might initially be missing from localStorage
, hence the user is undefined
error that I get about half the time.
After doing some research, there a couple of possible solutions I came up with.
localStorage
and show a loading page in the meantime. But I read on other SO answers that you should not delay React from rendering as this can cause other problems. So solution #2:localStorage
. Then, update the state with the user info to cause a re-render and display the user's actual avatar image.Are either of these methods a good/correct approach? If not, what other ways are there that I can use to deal with this?
Upvotes: 1
Views: 1205
Reputation: 1447
I would do something like this:
Use state to track when the response has arived
const [arrived, setArrived] = useState(false)
and change it inside setSession()
after the item is added to localStorage
:
this.auth0Client.client.userInfo(authResult.accessToken, (err, user) => {
if (err) {
console.log("setSession: Couldn't get user info");
return;
} else {
localStorage.setItem("user", JSON.stringify(user));
// over here
setArrived(true);
}
});
and run getUserInfo()
inside an useEffect()
:
useEffect(() => {
// run logic here to get the user from localStorage
// I would also keep this information inside state
}, [arrived])
Upvotes: 1