Auth0 async await return session user - reactjs

I have set up Auth0 using custom inputs with react and hooks. I am trying to protect my routes so that the user directs where they are based on our API and what information is provided back from that. In order to do that I am setting the user data inside of the setSession, but the item is coming back undefined/ has to do with auth0Client.client.userInfo not finishing before setSession function is done.
const setSession = async authResult => {
const expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
)
localStorage.setItem('access_token', authResult.accessToken)
localStorage.setItem('id_token', authResult.idToken)
localStorage.setItem('expires_at', expiresAt)
localStorage.setItem('auth', true)
setAuthenticated(true);
await auth0Client.client.userInfo(authResult.accessToken, (err, user) => {
localStorage.setItem('user', JSON.stringify(user))
setUser(JSON.stringify(user));
})
}
I call setSession in handle authentication:
const handleAuthentication = () => {
console.log("auth0Client", auth0Client);
if (typeof window !== 'undefined') {
auth0Client.parseHash(async (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
await setSession(authResult);
history.replace('/');
} else if (err) {
console.log(err)
return err;
}
})
}
}
I have tried return the value and calling it this way instead:
await setSession(authResult).then(
() => history.replace('/')
)
Either way, the first time it accesses the route that I want it has a null value for user.
I have put a complete mock repo here. You can create accounts and if you create extra accounts use the same email with +modifier/ example test+1#test.com
https://github.com/Sbphillips19/ManualAuth0Flow
I'm sure it's something simple, I have just tried probably 100 different ways over and over again and can't see it.

I'd give Jim and Jfriend00 the answer, but since it's a comment and I can't here is the answer using Bluebird:
await new Bluebird(function (resolve, reject) {
auth0Client.client.userInfo(authResult.accessToken, (err, user) => {
if (err) return reject(err)
resolve(user);
})
}).then(
data =>
{
localStorage.setItem('user', JSON.stringify(data))
setUser(JSON.stringify(data));
}
)
It looks like auth0 has talks about converting all their functions to be able to use async await, but for now bluebird will do!

Related

What is the best way about send multiple query in useMutation (React-Query)

I developed login function use by react-query in my react app
The logic is as follows
First restAPI for checking duplication Email
If response data is true, send Second restAPI for sign up.
In this case, I try this way
// to declare useMutation object
let loginQuery = useMutation(checkDuple,{
// after check duplication i'm going to sign up
onSuccess : (res) => {
if(res === false && warning.current !== null){
warning.current.innerText = "Sorry, This mail is duplicatied"
}else{
let res = await signUp()
}
}
})
//---------------------------------------------------------------------------------
const checkDuple = async() => {
let duple = await axios.post("http://localhost:8080/join/duple",{
id : id,
})
}
const signUp = async() => {
let res = await axios.post("http://localhost:8080/join/signUp",{
id : id,
pass : pass
})
console.log(res.data)
localStorage.setItem("token", res.data)
navigate("/todo")
}
I think, this isn't the best way, If you know of a better way than this, please advise.
Better to have another async function that does both things.
something like
const checkDupleAndSignUp = async () => {
await checkDuple();
await signUp();
};
And then use that in your useMutation instead.
Other things to consider:
Maybe move the logic to set local storage and navigate to another page in the onSuccess instead.
You can also throw your own error if one or the other request fails and then check which error happened using onError lifecycle of useMutation, and maybe display a message for the user depending on which request failed.
You can handle both of them in a single function and in mutation just add token in localStorage and navigate
like this:
const checkDupleAndSignUp = async () => {
return checkDuple().then(async res => {
if (res === false) {
return {
isSuccess: false,
message: 'Sorry, This mail is duplicatied',
};
}
const { data } = await signUp();
return {
isSuccess: true,
data,
};
});
};
and in mutation :
let loginQuery = useMutation(checkDupleAndSignUp, {
onSuccess: res => {
if (res.isSuccess) {
console.log(res.data);
localStorage.setItem('token', res.data);
navigate('/todo');
} else if (warning.current !== null) {
warning.current.innerText = res.message;
}
},
});

How to implement auto logout functionality if a user logs out and then logs in before previous setTimeout ends?

I am trying to implement an auto-logout functionality. I am implementing auto-logout using setTimeout.
Here is the code:
const onSubmitHandler = (e) => {
e.preventDefault();
console.log(emailId);
console.log(password);
axios.post('http://localhost:8080/login', { emailId, password })
.then(response => {
console.log(response);
localStorage.setItem('token', response.data.token);
axios.defaults.headers.common['Authorization'] = response.data.token;
setTimeout(() => {
localStorage.removeItem('token');
axios.defaults.headers.common['Authorization'] = null;
navigate('/');
}, 60000);
navigate('/');
})
.catch(err => {
console.log(err.response.data.message);
if (err.response.status === 500) {
navigate('/500');
}
setError(err.response.data.message);
})
}
The problem I am facing with my current implementation is that when I log in to my web app and then log out before auto-logout(i.e before setTimeout part fires) then again log in. Now when I stay I get navigated to "/" twice.
This means that the previous log in setTimeout did not die when I logged out manually.
Please guide me on how the previous setTimeout could be stopped once a new setTimeout starts, also let me know I more information is needed.
I saved the old token and the new token in the local storage. I also save the setTimeout in the local storage. The old token is used to determine if a new login has been made before the token expiration time and setTimeout is stored in the local storage to delete the previous setTimeout.
Here is my code:
const onSubmitHandler = (e) => {
e.preventDefault();
console.log(emailId);
console.log(password);
axios.post('http://localhost:8080/login', { emailId, password })
.then(response => {
console.log(response);
localStorage.setItem('token', response.data.token);
axios.defaults.headers.common['Authorization'] = response.data.token;
if (JSON.stringify(localStorage.getItem('oldToken')) !== JSON.stringify(localStorage.getItem('token'))) {
localStorage.setItem('oldToken', localStorage.getItem('token'));
clearTimeout(localStorage.getItem('timerId'));
const myTimeout= setTimeout(() => {
localStorage.removeItem('token');
axios.defaults.headers.common['Authorization'] = null;
navigate('/');
}, 60000);
localStorage.setItem('timerId', myTimeout);
}
navigate('/');
})
.catch(err => {
console.log(err.response.data.message);
if (err.response.status === 500) {
navigate('/500');
}
setError(err.response.data.message);
})
}

Firebase Passwordless Email Authentication Error in Expo App

I am setting up passwordless Auth in my Expo app using the Firebase SDK. I've gotten to the point where emails are being sent to the user's desired address with a redirect link back to the app. When the user clicks the link, they are indeed redirected but they are not being authenticated. I am receiving a generic error in the console :
ERROR: [Error: An internal error has occurred.]
But I know that my credentials are passing through properly as I have logged them out when the function runs:
isSignInWithEmailLink:true, url: exp://10.0.0.27:19000?apiKey=AIzaSyAmpd5DdsjOb-MNfVH3MgF1Gn2nT3TBcnY&oobCode=7FJTfBjM28gkn6GfBSAdgAk7wOegg9k4D5poVcylhSYAAAF8BO5gHQ&mode=signIn&lang=en
I am calling useEffect on this function:
useEffect(() => {
signInWithEmailLink();
}, []);
Send Link To Email (WORKING)
const sendSignInLinkToEmail = (email) => {
return auth
.sendSignInLinkToEmail(email, {
handleCodeInApp: true,
url: proxyUrl,
})
.then(() => {
return true;
});
};
User clicks on a link from the email to redirect to the app to Authenticate (NOT WORKING)
const signInWithEmailLink = async () => {
const url = await Linking.getInitialURL();
if (url) {
handleUrl(url);
}
Linking.addEventListener('url', ({ url }) => {
handleUrl(url);
});
};
(RETURNING ERROR)
const handleUrl = async (url) => {
const isSignInWithEmailLink = auth.isSignInWithEmailLink(url);
console.log('isSignInWithEmailLink: ', isSignInWithEmailLink, 'url', url);
if (isSignInWithEmailLink) {
try {
await auth.signInWithEmailLink(email, url);
} catch (error) {
console.log('ERROR:', error);
}
}
};
Have you enabled email sign in in your firebase console?
Are you storing the email in localStorage? It looks undefined in your logic.
Your listener should be in the useEffect hook.
I've code my code working looking like this:
const handleGetInitialURL = async () => {
const url = await Linking.getInitialURL()
if (url) {
handleSignInUrl(url)
}
}
const handleDeepLink = (event: Linking.EventType) => {
handleSignInUrl(event.url)
}
useEffect(() => {
handleGetInitialURL()
Linking.addEventListener('url', handleDeepLink)
return () => {
Linking.removeEventListener('url', handleDeepLink)
}
}, [])
You should use the onAuthStateChanged within useEffect rather than try and log the user in at that point in time. useEffect is used when you need your page to re-render based on changes.
For example:
useEffect(() => {
// onAuthStateChanged returns an unsubscriber
const unsubscribeAuth = auth.onAuthStateChanged(async authenticatedUser => {
try {
await (authenticatedUser ? setUser(authenticatedUser) : setUser(null));
setIsLoading(false);
} catch (error) {
console.log(error);
}
});
// unsubscribe auth listener on unmount
return unsubscribeAuth;
}, []);
You should invoke the user sign in method through other means such as a button to sign in, or validate user credentials at some other point within your app.
custom function:
const onLogin = async () => {
try {
if (email !== '' && password !== '') {
await auth.signInWithEmailAndPassword(email, password);
}
} catch (error) {
setLoginError(error.message);
}
};
Source: https://blog.jscrambler.com/how-to-integrate-firebase-authentication-with-an-expo-app

Axios refresh token issue

I'm using React.useEffect() to retrieve the users list.
React.useEffect(() => {
dispatch(UsersActions.creators.fetchingUsersAction());
UsersApi.methods.getUsers().then(
(res) => {
dispatch(UsersActions.creators.fetchUsersSuccessAction(res.data));
},
(e) => {
dispatch(UsersActions.creators.fetchUsersErrorAction());
}
);
}, [dispatch]);
On this example, fetchingUsersAction is used to set "loading" to true, and fetchUsersErrorAction to false. This works fine, except when the request fails due to token expiration.
ApiClient.interceptors.response.use(
function (response) {
return response;
},
function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refresh = JSON.stringify({
refreshToken: localStorage.getItem("refresh"),
});
AuthenticationApi.methods.refresh(refresh).then((res) => {
if (res.data.accessToken) {
localStorage.setItem("token", res.data.accessToken);
}
ApiClient.defaults.headers.common["Authorization"] =
"Bearer " + res.data.accessToken;
originalRequest.headers["Authorization"] =
"Bearer " + res.data.accessToken;
return ApiClient(originalRequest);
});
}
return Promise.reject(error);
}
);
This is sending a request to generate a new token and the previous request, but since the first request failed, the useEffect is going to the error section, making the "loading" false and showing the users list based on the previous state. What is the best way to deal with this problem?
Thanks
You should create an Async fucntion inside useEffect hook and use await to wait for the response, then call the function. Here is one example:
useEffect(() => {
const getRoles = async () => {
await authService.roles().then((res) => {
//Do your stuff.
console.log(res);
}).catch((error) => {
console.log(`'Catching the error: '${error}`);
});
};
//Call the recent created function.
getRoles();
}, []);
Your interceptor looks good to me.

How to upload image to Firebase during sign up?

I am trying to upload an image to Firebase storage. The problem is that since the user has not signed up yet, I don't have their uid.
I depend on onAuthStateChanged to get the user id and upload an image to their bucket, but so far it hasn't turned out well.
const { userObject } = useContext(Context) //trying to get the uid from here
onSubmit={(values, { setSubmitting }) => {
async function writeToFirebase() {
firebaseService.auth.createUserWithEmailAndPassword(values.email, values.password)
await firebaseService.firestore.collection('businessesPendingAdminApproval').add(values)
}
writeToFirebase()
async function sendToFirebaseImageBucket(photo, uid) {
const businessRef = await firebaseService.firestore.doc(
`businessesPendingAdminApproval/${uid}`,
)
firebaseService.storage
.ref()
.child('businesses')
.child(uid)
.child('avatar-image')
.put(photo)
.then(response => response.ref.getDownloadURL())
.then(photoURL => businessRef.update({ avatarImage: photoURL })) //try to update avatarImage
}
const uid = userObject.uid //undefined, can't get uid
sendToFirebaseImageBucket(values.avatarImage, uid) //uid gets passed as undefined
}}>
The way I am setting the userObject which is where I'm trying to get the uid from.
Setting the userObject eventually works but maybe not fast enought for me to be able to pass it to a function (as in the code above).
useEffect(() => {
firebaseService.auth.onAuthStateChanged(async function (userAuth) {
if (userAuth) {
const user = await firebaseService.createUserProfileDocument(userAuth)
setUserObject(user) //set userObject which has an uid field.
} else {
console.log('no one signed in')
}
})
}, [])
Just add your image to cloud storage right after you have logged in and was able to get uid. the following code can help you, it works for me as well. put the following code inside useEffect.
const unsubscribe = auth().onAuthStateChanged(user => {
if (user.uid){
const ref = storage.ref(`images/${user.uid}`);
const task = ref.putFile(_image, { contentType: 'image/jpeg' });
task.on(firebase.storage.TaskEvent.STATE_CHANGED, snap => {
setState({ type: 'cents', value: snap.bytesTransferred / snap.totalBytes * 100 });
}, err => { console.log('Error in help:persisAppState: ', err) }, async () => {
const image = await ref.getDownloadURL();
if (image) await db.collection("imagelinks").doc(user.id).set({ image });
});
}
});

Resources