firebase auth applyActionCode method does not turn emailVerified to true - javascript

I am developing firebase authentication system where a user is sent email to verify email adr. I got everything working eventually. The user signs up and the email (with the link) is sent to the signed up edmail adr. I use custom email action handler (https://firebase.google.com/docs/auth/custom-email-handler) to respond to a click on the link. On my node.js express route, I get oobCode (which firebase documentation say is "A one-time code, used to identify and verify a request") and pass it as an argument to firebase.auth().applyActionCode(oobCode) which returns a void promise when resolved. see code below.
firebase.auth().applyActionCode(oobCode)
.then( () => {
return admin.auth().updateUser(currentUser.uid, {
emailVerified: true,
})
})
.then( () => {
return res.status(301).redirect(`../unps/${currentUser.uid}`)
})
.catch( (error) => {
return res.status(500).json({ unp_error: `error message: ${error.message} - error code:
${error.code}` })
});
My undestanding of the documentation is that, applyActionCode method, if resolved, will set the emailVerified to true but this does not happen even though there is no error. I had to call updateUser to change emailVerified to true. Shouldnt this be done automatically by the method applyActionCode if a valid oobCode is presented as argument? What am I missinmg? Pleaase help?

All information about the Firebase Authentication user in your application code is taken from the ID token. This ID token is valid for an hour, and automatically refreshed by the SDK about 5 minutes before it expires. Until the token is refreshed, it may not reflect the latest value of emailVerified or other information about that user profile on the server.
It is indeed normal that you need to force a refresh of the token, to get the updated status before it auto-refreshes. When you do that, you shouldn't have to call admin.auth().updateUser(...) though.

Related

How do I catch specific error returned by Firebase Auth, when user updates their password?

I am writing an app, where I want to give the user possibility to change their password. So I have a simple UpdatePassword.js page, where I invoke Firebase Authentication .updatePassword(password) method. As explained in the docs, this is a sensitive operation, and as such, the user needs to authenticate (if they haven't authenticated recently), in order to change their password to a new one.
This is my method:
const update = async () => {
const user = await firebase.auth().currentUser;
await user
.updatePassword(password)
.then(() => {
setUpdated(true);
})
.catch((error) => {
//I want to handle this specific error but I don't know how
if (
error.message ===
"This operation is sensitive and requires recent authentication. Log in again before retrying this request."
) {
console.log("should display a modal for user to authenticate again");
}
console.log("error while updating pass: ", error);
setSaving(false);
});
};
As you can see from my console.logs, in the case where the user needs to authenticate again, I want to display a modal, where they will sign in with their credentials again. This is not a problem and is easy to do. However, my question is, how do I catch this specific type of error where the user needs to authenticate? As per my console.logs, the way I have implemented it right now, I am just comparing the error message which I receive from Firebase Authentication, which is really not the right way to do. What if Firebase Auth change the error message to something else? Is there something like an error code which I can compare to the error thrown, and handle the exception by error code or something more safe than just a string message?
As you will see in the doc, the error that is thrown in this case (i.e. "if the user's last sign-in time does not meet the security threshold") has an auth/requires-recent-login error code.
So:
//...
.catch((error) => {
if (error.code === 'auth/requires-recent-login') {
// Display the modal
} else {
// ...

Firebase Web SDK: refreshing auth token so that email_verified is updated in firestore rules

Using Firebase Web SDK, I'm requiring users to verify their email before accessing Firestore documents. I have a Firestore rule that gates the document like this:
allow read: if request.auth != null && request.auth.token.email_verified;
I'd like the email verification to be reflected as soon as the user verifies his/her email without requiring the user to sign out and sign back in. Unfortunately onAuthStateChanged() doesn't fire when emailVerified changes, so I'm refreshing the client token by polling for changes to emailVerified. Something like this:
Note: My examples use the new beta Firebase Web SDK V9 (Modular Web SDK) in case the syntax is unfamiliar.
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
});
}, 2000);
With the code above, I can get emailVerified to be reflected property inside my web app, but the problem arises when I try to make a request to Firestore:
const unsubscribe = onSnapshot(
doc(db, 'widgets', 'widget1'),
snap => {
console.log(snap);
},
);
That request results in a Firestore permission error. Once the user signs out and signs back in, the Firestore request is accepted.
How can I get the auth token that gets sent to Firestore to be updated with the latest email_verified without the user to sign out and and sign back in?
It turns out that a series of steps need to happen to refresh the token. After email verification, you need to reload the user AND explicitly get a new id token with getIdToken(user, true) after you reload the user. Only after those 2 steps will an updated token be sent to Firestore for queries. You also need to unsubscribe and re-subscribe to onAuthStateChanged manually, as that doesn't get triggered on token change. The modified version of my example is:
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
getIdToken(auth.currentUser, true).then(() => {
// now the new token will be sent to Firestore, yay!
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
})
});
}, 2000);
Please post your answer if there's an easier way. I especially don't like the polling part.

Firebase Email Link Authentication

I am using passwordless authentication for a project, everything is working as expected, however I have one question about this authentication. I will talk about the scenario.
First step: as we all know, a new user needs an email and then proceeds to click the link to login.
That is the normal case, no problem with it, but what if a user has already done that step and say he/she logs out from the app? it seems like they need to do the first step I described above again.
Here is what I have tried so far:
login() {
const email = this.email;
this.$store
.dispatch("LOGIN", { email })
.then(resp => {
this.$router.replace("/");
})
.catch(err => {
this.autherror = true,
this.errorMessage = err.message;
});
}
LOGIN: ({ commit }, user) => {
return new Promise((resolve, reject) => {
// commit(AUTH_REQUEST)
firebase.auth().signInWithEmailLink(user.email, window.location.href)
.then((result) => {
window.localStorage.removeItem("emailForSignIn");
resolve(result);
})
.catch((err) => {
reject(err);
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
});
},
I will get an error "Invalid email link!" trying the above code and even if I put the url as the previous one I logged in with, It will also throw an error "The action code is invalid. This can happen if the code is malformed, expired, or has already been used"
I can understand the point why an email to login is always required but the main point am trying to say is, if a user log's in from the link at first and then log's out, they can sign in the app without needing to do first step again, how? that means if there is a way to store credentials in cookies/localstorage, and the only time they need to do the first step again is if they clear the cookies, storage etc. from all or that particular app/page requiring.
So is it possible? It is something that will definitely improve user experience.
You should read and understand how users work in Firebase (and basically the same in any oAuth type verification system) - https://firebase.google.com/docs/auth/users
and more particularly, how email is used - https://firebase.google.com/docs/auth/web/email-link-auth
In your code you should use the email confirmation steps as shown in the reference above (so, something like the code below - you may need some minor changes to fit your local scenario):
LOGIN: ({ commit }, user) => {
return new Promise((resolve, reject) => {
// Confirm the link is a sign-in with email link.
if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
// Additional state parameters can also be passed via URL.
// This can be used to continue the user's intended action before triggering
// the sign-in operation.
// Get the email if available. This should be available if the user completes
// the flow on the same device where they started it.
var email = window.localStorage.getItem('emailForSignIn');
if (!email) {
// User opened the link on a different device. To prevent session fixation
// attacks, ask the user to provide the associated email again. For example:
email = window.prompt('Please provide your email for confirmation');
}
// commit(AUTH_REQUEST)
firebase.auth().signInWithEmailLink(email, window.location.href)
.then((result) => {
window.localStorage.removeItem("emailForSignIn");
resolve(result);
})
.catch((err) => {
reject(err);
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
});
}
},
Just indicate in your db that the person has been verified in case you don't want the local storage to store the data

firebase - Firebase Current User not storing the correct user

I have a project using Firebase for the authentication. When I log into my project, sometimes the firebase.auth().currentUser method returns info from some other user. Sometimes I get the info from the actual current user and sometimes the previously signed in user - when this happens, the only way to correct the problem and get data for the correct user is to logout and sign back in again.
My login method:
onLoginClick = () => {
firebase.auth().signInWithEmailAndPassword(this.state.email.trim(), this.state.password.trim())
.then((user) => {
//Some redirects depending on my users role
})
.catch((error) => {
console.log(error, error.message);
if (error.code == "auth/user-not-found" || error.code == "auth/wrong-password") {
//Handle error
}
});
}
And my Logout method:
onLogoutClick = () => {
firebase.auth().signOut().then(function () {
this.props.history.replace("/")
}).catch(function (error) {
console.log(error);
});
}
They're pretty standard but seems like I'm doing something wrong. I've tried clearing the sessionStorage and the localStorage on the logout method but that didn't help.
UPDATE:
I've tried with some suggestions like doing:
firebase.auth().signOut().then(() => {
firebase.auth().signInWithEmailAndPassword(email, password).then((user) => {
//My redirects and everything
})
})
But this doesn't work.
Also, I've noticed that when I log in and use wrong credentials, the sessionStorage gets an entry with the previously signed in user, even though I signed him out.
I'm pretty sure that during the initialization of a new login session, until it is complete, currentUser hasn't been updated yet; use the user parameter when one is passed in on those methods. Because the login is still in progress, there is not yet a currentUser, so it passes the user in progress via the user parameter. (I think this means it is likely that it will be null the first time someone logs in, and it will be the previous one thereafter, unless it's cleared on logout by some code.)
Use the user parameter when it is provided, such as in the onAuthStateChanged handler. See the first example here, and the starred note in the blue box farther down that same page.

Firebase 4.1.3: onAuthStateChange not called after first sign in with email and password

I upgraded the version of Firebase for my app from 3.5.0 to 4.1.3 and noticed that the onAuthStateChange callback function is no longer called after a user successfully signs in for the first time after verifying their email address.
The app is written in JavaScript.
These are the relevant sections of my code:
Callback setup
firebase.auth().onAuthStateChanged(onAuthStateChange);
Callback
function onAuthStateChange (user) {
console.log(user); // Not appearing in console
}
Sign in
firebase.auth().signInWithEmailAndPassword(email, password)
.then(function (user) {
console.log("signInWithEmailAndPassword success"); // This appears in the console
})
.catch(function(error) {
console.log("signInWithEmailAndPassword", error);
self.signInError = error.message;
$timeout(function () {
$scope.$apply();
});
});
Edit - these are the steps to reproduce the problem (the typical action of a user of my app):
User downloads and launches app
User registers with email and password
App sends email verification email
User receives verification email and clicks on link
User goes to sign in page of app
User signs in triggering the console.log("signInWithEmailAndPassword success");
onAuthStateChanged callback is not called
For development and testing purposes (not what a user would do but I have done)
User reloads the app
User is now in the app
User signs out of the app
User signs in to the app triggering the console.log("signInWithEmailAndPassword success");
onAuthStateChanged callback is called
The problem is that auth().createUserWithEmailAndPassword() does in fact log in a type of user in.
You will find that if you type
firebase.auth().onAuthStateChanged(user => {
console.log("onAuthStateChanged()");
that createUserWithEmailAndPassword() does trigger the console log. However, there seems to be no valid "user" object, which would explain why nothing appears for you since you are only logging the user.
I ran into the exact same problems. At the sendEmailverification() step notice how it does require you to use auth().currentUser, signalling there must be some sort of user signed in (I am not sure how firebase handles the difference between email verified users and non-verified users behind the scenes)
You can simply called the signOut() function after sending the email verification and it should allow the onAuthStateChanged() function to call when logging in for the first time (without reloading the app)
firebase.auth().currentUser.sendEmailVerification()
.then(() => {
console.log("Email verification sent");
firebase.auth().signOut().then(() => {
console.log("Signed out!");
}).catch((err) => {
console.log("Error signing out!", err);
});
It is rather confusing that you can actually "Log in" successfully without causing a change in AuthStateChanged or returning any errors.
TLDR: Remember to use the auth().signOut() function after sending the email verification.
Try this way, i hope it'll work
firebase.auth().onAuthStateChanged(function(user){
console.log(user);
})

Categories

Resources