can someone help me how to convert an anonymous account (signInAnonymouslyAndRetrieveData) to a permanent account ?
I have tried this:
firebase.auth().currentUser.linkAndRetrieveDataWithCredential(credential).then(function(usercred) {
var user = usercred.user;
console.log("Anonymous account successfully upgraded", user);
}, function(error) {
console.log("Error upgrading anonymous account", error);
});
but i'm getting an
cannot read property "linkAndRetrieveDataWithCredential" of null
error.
firebase.auth().currentUser will be null if there's no currently signed in user.
Ensure your anonymous user is still signed in then as in your example above you can then upgrade your user using linkAndRetrieveDataWithCredential
const credential = firebase.auth.EmailAuthProvider.credential(email, password);
const currentUser = firebase.auth().currentUser;
if (currentUser) {
currentUser.linkAndRetrieveDataWithCredential(credential).then((userCredential) => {
const user = userCredential.user;
console.log("Account linking success", user);
}, (error) => {
console.log("Account linking error", error);
});
}
References:
Official Firebase guide to account linking:
https://firebase.google.com/docs/auth/web/account-linking
React Native Firebase reference doc:
https://rnfirebase.io/docs/v4.3.x/auth/reference/User#linkAndRetrieveDataWithCredential
Related
I want to securely create a user document onCreate that is in sync with the auth.user database in Firebase v9.
I think it wouldn't be secure to let a registered user create a user document. So I wrote a cloud function which triggers on functions.auth.user().onCreate() and creates a user document.
Problem:
I have the problem keeping them in sync as the onSnapshotmethod which should await for the user document to exists already returns a promise if the user document does not yet exists. Sometimes it works and sometimes not. So I don't know when I can update the by the cloud function created user document.
Question:
Why does the onSnapshot sometimes work and sometimes not. How can I fix it?
Here is a link to a helpful Article which seem to doesn't work in v9. Link
I tried and searched everywhere. I can't believe this is not a standard feature and still a requested topic. This seems so basic.
Error
error FirebaseError: No document to update: as const user = await createAccount(displayName, email, password); returns even if user is not yet in doc.data()
Sign Up function
interface SignUpFormValues {
email: string;
password: string;
confirm: string;
firstName: string;
lastName: string;
}
const createAccount = async (
displayName: string,
email: string,
password: string
) => {
// Create auth user
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// -> Signed in
// Update Profile
const user = userCredential.user;
const uid = user.uid;
await updateProfile(user, {
displayName: displayName,
});
// IMPORTANT: Force refresh regardless of token expiration
// auth.currentUser.getIdToken(true); // -> will stop the onSnapshot function from resolving properly
// Build a reference to their per-user document
const userDocRef = doc(db, "users", uid);
return new Promise((resolve, reject) => {
const unsubscribe = onSnapshot(userDocRef, {
next: (doc) => {
unsubscribe();
console.log("doc", doc); // -> returning undefined
console.log("doc.data()", doc.data()); // -> returning undefined
resolve(user); // -> returning undefined
},
error: (error) => {
unsubscribe();
console.log("error", error);
reject(error);
},
});
});
};
const handleSignUp = async (values: SignUpFormValues) => {
const { firstName, lastName, email, password } = values;
const displayName = `${firstName} ${lastName}`;
try {
setError("");
setLoading(true);
// Create user account
const user = await createAccount(displayName, email, password);
console.log("createAccount -> return:", user); // -> problem here sometimes return undefined
// Update user
const newUserData = {
displayName: displayName,
firstName,
lastName,
};
// Build a reference to their per-user document
const userDocRef = doc(db, "users", user.uid);
await updateDoc(userDocRef, newUserData);
// Send Email verification
await authSendEmailVerification(user);
// Logout
await logout();
navigate("/sign-up/email-verification", { state: values });
} catch (error: any) {
const errorCode = error.code;
const errorMessage = error.message;
console.log("error", error);
console.log("error", errorCode);
if (errorCode === "auth/email-already-in-use") {
const errorMessage =
"Failed to create an account. E-Mail address is already registered.";
setError(errorMessage);
console.log("error", errorMessage);
} else {
setError("Failed to create account.");
}
}
setLoading(false);
};
Cloud function which triggers the user onCreate
// On auth user create
export const authUserWriteListener = functions.auth
.user()
.onCreate(async (user, context) => {
console.log("user:", user);
const userRef = db.doc(`users/${user.uid}`);
await userRef.set({
email: user.email,
createdAt: context.timestamp,
firstTimeLogin: true,
});
return db.doc("stats/users").update({
totalDocsCount: FieldValue.increment(1),
});
});
The issue is that the Cloud Function code runs asynchronously. There is no guarantee that it will run quickly enough to have the document created in Firestore between the end of createAccount() and your call to updateDoc(). In fact, if your system has been idle for a while it could be a minute (or more!) for the Cloud Function to execute (do a search for "cold start firebase cloud functions").
One option, depending on your design, might be to not take in first name and last name during sign up? But instead take the user to a "profile page" once they are logged in where they could modify aspects of their profile (by that time the user profile document hopefully is created). On that page, if the get() returns no document, you could put up a notification to the user that the system "is still processing their registration" or something like that.
I'm trying to make just a simple authentication app with electron and firebase redirect, but if the user is already logged in and I use the firebase.auth().currentUser.getIdToken() to get the IdToken of that user, but when i try that token in
firebase.auth().signInWithCredential(credential) I get the error that says ERROR: auth/invalid-credential
Here is my code front-end
firebase.auth().onAuthStateChanged( async function (user) {
if (user) {
// User is signed in.
var user = await firebase.auth().currentUser;
if (user != null) {
await firebase.auth().currentUser.getIdToken().then(function(idToken) {
window.location.href = "electron://"+idToken;
}).catch(function(error) {
console.log(error)
});
}
} else {
// No user is signed in.
document.getElementById("user_div").style.display = "none";
document.getElementById("login_div").style.display = "block";
}
});
Here is my code back-end
app.on('second-instance', (event, commandLine, workingDirectory) => {
if (commandLine[2]) {
var url = commandLine[2].split('/')
var id_token = url[2]
console.log('id: ', id_token)
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(id_token);
// Sign in with credential from the Google user.
firebase.auth().signInWithCredential(credential)
.then((success)=>{
myWindow.loadFile('./scr/welcome.html')
console.log('RESULT: ',success)
})
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log('ERROR:', errorMessage)
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
console.log('ERROR:', credential)
// ...
})
}
I'm missing something or doing something wrong?
That's not how ID tokens work. The purpose of an ID token is to pass to your backend, so that it can validate the identity of the signed in user, and perform some action on their behalf. It's not valid for signing in on the client again. You might want to review the documentation on use of ID tokens to learn how this works.
signInWithCredential only works with Google Auth when you correctly construct a GoogleAuthProvider credential. There is plenty of sample code in the API documentation for that.
I'm developing an authentication system for my React app with Firebase Auth. When the user signs up, firebase auth registers the user (createUserWithEmailAndPassword) and returns a promise with auth.uid and auth.emailVerified set to "false". This is fine.
I then use sendEmailVerification() method so the email address can be verified. I've tested the code and it works ok for a "valid" and "existing" email address. "catch" does give error for duplicate email address as expected. The problem is, it does not send the NON-EXISTING email (which I suppose is the correct bahaviour) but its should then give an error (so I can display to the user) which it does not.
Can someone explain why I'm not getting error for NON EXISTENT email address?
export const unpSignUp = (newUser) => {
console.log("newUser", newUser);
return (dispatch, getState, { getFirebase, getFirestore }) => {
const firebase = getFirebase();
const firestore = getFirestore();
firebase.auth().createUserWithEmailAndPassword(
newUser.unp_a05_email,
newUser.unp_a10_password
)
.then((response) => {
return firestore
.collection("unps")
.doc(response.user.uid)
.set({
unp_a02_surname: newUser.unp_a02_surname,
unp_a03_name: newUser.unp_a03_name,
unp_a06_verify_email_sent_datetime: null,
unp_a07_email_verified_on_datetime: null,
unp_a18_acc_created_on_datetime: moment().format("YYYY MM DD HH:mm:ss"),
});
})
.then((response) => {
console.log("SIGNUP SUCCESS ", response);
// user has been signed up, next step is to send verification email
dispatch({ type: SIGNUP_SUCCESS })
})
.then(() => {
// user has been signed up, next step is to send verification email
console.log('send email adr verification')
return firebase.auth().currentUser.sendEmailVerification()
})
.then( (response) => {
console.log("Verification email sent", response);
const user = firebase.auth().currentUser
console.log('current user', user)
const unp = firestore.collection("unps").doc(user.uid);
return unp.update({
unp_a06_verify_email_sent_datetime: moment().format("YYYY MM DD HH:mm:ss"),
})
})
.then( () => {
console.log(`unp_a06_verify_email_sent_datetime update to ${moment().format("YYYY MM DD HH:mm:ss")} `)
})
.catch((error) => {
console.log("SIGNUP ERROR", error);
console.log("SIGNUP ERROR CODE", error.code);
console.log("SIGNUP ERROR MESAGE", error.message);
dispatch({ type: SIGNUP_ERROR, error });
});
};
};
From firebase reference,
the return type of sendEmailVerification is Promise<void> - and it does not tell about any error codes that are sent in case of wrong email or failed/bounce mails. The error code it mentions is with respect to actionCodeSettings
If the actionCodeSettings is not specified, no URL is appended to the action URL. The state URL provided must belong to a domain that is whitelisted by the developer in the console. Otherwise an error will be thrown.
Thus it can not be checked if it is a valid email id. (This is expected behaviour as receiving mail servers may be down at times and hence there is a concept of retrying bounce mails)
I am using Firebase to authenticate users in our app using GoogleAuthProvider. But I don't want a new user to sign in if they are not already an authenticated user.
If the user exists then sign them in and console.log('user ' + user.email + ' does exist!');.
However, if the user does not exist. Then do not allow authentication and console.log('user ' + user.email + ' does not exist!')
var googleProvider = new firebase.auth.GoogleAuthProvider();
export const doSignInWithGoogle = () => auth.signInWithPopup(googleProvider);
googleLogin = () => {
auth
.doSignInWithGoogle()
.then(result => {
var user = result.user;
const userRef = db.collection('users').doc(user.uid);
userRef.get().then(docSnapshot => {
if (docSnapshot.exists) {
userRef.onSnapshot(() => {
console.log('user ' + user.email + ' does exist!');
});
} else {
console.log('user ' + user.email + ' does not exist!');
}
});
})
.catch(error => {
this.setState(updateByPropertyName('error', error));
});
};
I thought referencing the user records in Firestore would be a simple approach to this. However, perhaps Firebase Auth already have a way to do this. I cannot find documentation or any example.
In the above code, nothing gets logged and the user is either created or logged in.
How can I stop new users from signing up, whilst still allowing current users to sign in?
If you really want to use signInWithPopup method, you have this option,
but it's not the best way. when you are signing in with google, signInWithPopup method returns a promise. you can access the isNewUser property in additionalUserInfo from resulting object. then delete the user you just created.
firebase.auth().signInWithPopup(provider).then(
function (result) {
var token = result.credential.accessToken;
var user = result.user;
//this is what you need
var isNewUser = result.additionalUserInfo.isNewUser;
if (isNewUser) {
//delete the created user
result.user.delete();
} else {
// your sign in flow
console.log('user ' + user.email + ' does exist!');
}
}).catch(function (error) {
// Handle Errors here.
});
This is the easy way but deleting after creating is not the best practice. There is another option,
you can use, signInAndRetrieveDataWithCredential method for this. according to the docs,
auth/user-not-found will be
Thrown if signing in with a credential from
firebase.auth.EmailAuthProvider.credential and there is no user
corresponding to the given email.
function googleSignInWithCredentials(id_token) {
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(id_token);
// Sign in with credential from the Google user.
firebase.auth().signInAndRetrieveDataWithCredential(credential)
.then(function (userCredential) {
//sign in
console.log(userCredential.additionalUserInfo.username);
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
if (errorCode === 'auth/user-not-found') {
//handle this
} else {
console.error(error);
}
});
}
here is an example from firebase github repo.
with Firebase security rules, can only check if keys exist - therefore searching in the users table is not an option:
"emails": {
"example1#gmail.com": true,
"example2#gmail.com": true
}
and then one can check with security rules, if the auth.token.email exists as a key:
{
"rules": {
".read": "root.child('emails').child(auth.token.email).exists(),
".write": false,
}
}
in the client, this should throw an "The read failed: Permission denied error" error then, to be handled accordingly. hooking into the Firebase sign-up isn't possible - but while they cannot log-in, this has the same effort (except that on has to clean up the user-database from time to time); eg. with a Cloud Function, which deletes users, which do not have their email as key in the emails "table".
in Firestore security rules, one can check with:
request.auth.token.email & request.auth.token.email_verified
for example, with a collection called emails and a collection called content:
match /databases/{database}/documents {
function userMatchesId(userId) {
return request.auth != null && request.auth.uid == userId
}
function readAllowed(email) {
return if get(/databases/$(database)/documents/emails/$(request.auth.token.email)).data != null
}
match /users/{userId} {
allow get: if userMatchesId(userId)
}
match /content {
allow get: if readAllowed(request.auth.token.email)
}
}
The object you receive from firebase after login has additionalUserInfo where you have the property isNewUser.
You can find the reference here: https://firebase.google.com/docs/reference/js/firebase.auth.html#.AdditionalUserInfo
I am trying to change/update a user's email address using :
firebase.auth().changeEmail({oldEmail, newEmail, password}, cb)
But I am getting ...changeEmail is not a function error. I found the reference here from the old firebase docu.
So how to I do it in the 3.x version? Because I cant find a reference in the new documentation.
You're looking for the updateEmail() method on the firebase.User object: https://firebase.google.com/docs/reference/js/firebase.User#updateEmail
Since this is on the user object, your user will already have to be signed in. Hence it only requires the password.
Simple usage:
firebase.auth()
.signInWithEmailAndPassword('you#domain.example', 'correcthorsebatterystaple')
.then(function(userCredential) {
userCredential.user.updateEmail('newyou#domain.example')
})
If someone is looking for updating a user's email via Firebase Admin, it's documented over here and can be performed with:
admin.auth().updateUser(uid, {
email: "modifiedUser#example.com"
});
FOR FIREBASE V9 (modular) USERS:
The accepted answer will not apply to you. Instead, you can do this, i.e., import { updateEmail } and use it like any other import. The following code was copy/pasted directly from the fb docs at https://firebase.google.com/docs/auth/web/manage-users
Happy coding!
import { getAuth, updateEmail } from "firebase/auth";
const auth = getAuth();
updateEmail(auth.currentUser, "user#example.com").then(() => {
// Email updated!
// ...
}).catch((error) => {
// An error occurred
// ...
});
You can do this directly with AngularFire2, you just need to add "currentUser" to your path.
this.af.auth.currentUser.updateEmail(email)
.then(() => {
...
});
You will also need to reauthenticate the login prior to calling this as Firebase requires a fresh authentication to perform certain account functions such as deleting the account, changing the email or the password.
For the project I just implemented this on, I just included the login as part of the change password/email forms and then called "signInWithEmailAndPassword" just prior to the "updateEmail" call.
To update the password just do the following:
this.af.auth.currentUser.updatePassword(password)
.then(() => {
...
});
updateEmail needs to happen right after sign in due to email being a security sensitive info
Example for Kotlin
// need to sign user in immediately before updating the email
auth.signInWithEmailAndPassword("currentEmail","currentPassword")
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success now update email
auth.currentUser!!.updateEmail(newEmail)
.addOnCompleteListener{ task ->
if (task.isSuccessful) {
// email update completed
}else{
// email update failed
}
}
} else {
// sign in failed
}
}
async updateEmail() {
const auth = firebase.auth();
try {
const usercred = await auth.currentUser.updateEmail(this.email.value);
console.log('Email updated!!')
} catch(err) {
console.log(err)
}
}
You can use this to update email with Firebase.
Firebase v9:
const changeEmail = (userInput) => {
const { newEmail, pass } = userInput
signInWithEmailAndPassword(auth, oldEmail, pass)
.then(cred => updateEmail(cred.user, newEmail))
}