Using vuex, firestore, and createUserWithEmailAndPassword, how do I create a user profile collection when a user registers? - firebase

For the app I'm building, I want my users to have a profile created for them when they register; the profile would contain the user's username, email, and the uid created by firebase authentication. I've got the authentication portion using createUserWithEmailAndPassword to work on its own. I'm also able to create a "users" collection, capturing a username and the user's email, on its own as well. However, I'm running into trouble grabbing and saving the uid to the user's profile in the user's collection.
Here is the code I have at the moment:
import * as firebase from "firebase/app";
import db from "../../components/firebase/firebaseInit";
actions: {
registerUser({ commit }, payload) {
commit("setLoading", true);
commit("clearError");
firebase
.auth()
.createUserWithEmailAndPassword(payload.email, payload.password)
.then(user => {
commit("setLoading", false);
const newUser = {
email: user.email,
id: user.uid,
courses: []
};
commit("setUser", newUser);
db.collection("users")
.add({
username: payload.username,
email: user.email,
userId: user.uid
})
.then(() => {
console.log("New user added!");
})
.catch(err => {
console.log(err);
});
})
.catch(err => {
commit("setLoading", false);
commit("setError", err);
});
},
In the research I've done, I've found these suggested solutions:
Get Current User Login User Information in Profile Page - Firebase and Vuejs
Cloud Firestore saving additional user data
And this video:
https://www.youtube.com/watch?v=qWy9ylc3f9U
And I have tried using the set() method instead of add(), as well.
But none of them seem to work, for me at least.
Thank you in advance for your help.
And if you need to see any more code, just let me know.

You haven't shared the error message you get, but most probably the error comes from the fact that the createUserWithEmailAndPassword() method returns a UserCredential and not a User.
So you have to do as follows:
import * as firebase from "firebase/app";
import db from "../../components/firebase/firebaseInit";
actions: {
registerUser({ commit }, payload) {
commit("setLoading", true);
commit("clearError");
firebase
.auth()
.createUserWithEmailAndPassword(payload.email, payload.password)
.then(userCredential=> {
commit("setLoading", false);
const user = userCredential.user; // <-- Here is the main change
const newUser = {
email: user.email,
id: user.uid,
courses: []
};
commit("setUser", newUser);
return db.collection("users")
.add({
username: payload.username,
email: user.email,
userId: user.uid
});
})
.then(() => {
console.log("New user added!");
})
.catch(err => {
commit("setLoading", false);
commit("setError", err);
});
},

Related

How can I log in a user right after his/her email has been verified using firebase/auth and react-native without creating a whole landing page?

Notice: I have seen this question, but creating a whole landing page just to verify a user seems a bit much.
I added a login functionality to my react-native app using firebase/auth with email and password. This works well so far and I have no issues doing that.
I then continued to send a verification email to a new user and only allow him/her to use the app, once the email is verified. Again, no issues here.
The next step would be to login the user right after the email was verified. This is where I'm stuck, since the onAuthStateChanged eventhandler doesn't update after the user pressed the verification link in the email.
Is there any way to listen to the emailVerified state in real-time? I tried to use polling with setInterval() but this is not great since there is a notable delay between verification and login. I read about a continueLink you can pass to sendEmailVerification, but I couldn't figure out how to make that work in react-native.
I'm using Expo and therefore the Firebase SDK, not the Firebase react native package.
Here is the code I use for the signup:
export const signUp = async (username: string, email: string, password: string) => {
try {
const auth = getAuth();
if (email && password && username) {
// sign up
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
// save username in firestore
await setUserName(userCredential, username);
// send Email Verification
await sendEmailVerification(userCredential.user);
return true;
}
} catch (error) {
onError(error);
}
};
And this is my onAuthStateChanged handler:
auth.onAuthStateChanged(authenticatedUser => {
try {
if (authenticatedUser?.emailVerified) {
setUser(authenticatedUser)
} else {
setUser(null)
}
} catch (error) {
console.log(error);
}
});
So in the end I did follow this question, but I changed it a bit to fit my needs. I'll post my steps for anyone who's doing the same.
Create a simple static website with firebase init and host it on firebase or somewhere else (check the hosting tab in your firebase console to get started)
Follow this guide to create the appropriate handlers on the website
Add the following to your verificationHandler to update the user (don't forget to import firestore) (I send the userId via the continueURL, but there are probably better ways)
// You can also use realtime database if you want
firebase.firestore().collection("users").doc(userId).set({
emailVerified: true
}, {merge: true}).then(() => {
message.textContent = "Your email has been verified.";
}).catch((error) => {
message.textContent = "The verification was invalid or is expired. Please try to send another verification email from within the app.";
});
Got to authentication -> templates in your firebase console and change the action url to your hosted website's url
Add a listener to the firestore doc to your react-native app
const onUserDataChanged = (uid, callback) => {
onSnapshot(doc(firestore, "users", uid), doc => callback(doc.data()));
}
Use the data from the callback to update the login state in the app
// As an example
auth.onAuthStateChanged(authenticatedUser => {
if (authenticatedUser && !authenticatedUser.emailVerified) {
unsubscribeFirestoreListener?.();
unsubscribeFirestoreListener = onUserDataChanged(authenticatedUser.uid, (data: any) => {
if (data?.emailVerified) {
setUser(authenticatedUser);
unsubscribeFirestoreListener?.();
}
});
}
}
use the codes below for your authentication context. for user id, you should use 'user.uid'
import React, { useState, createContext } from "react";
import * as firebase from "firebase";
import { loginRequest } from "./authentication.service";
export const AuthenticationContext = createContext();
export const AuthenticationContextProvider = ({ children }) => {
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
firebase.auth().onAuthStateChanged((usr) => {
if (usr) {
setUser(usr);
setIsLoading(false);
} else {
setIsLoading(false);
}
});
const onLogin = (email, password) => {
setIsLoading(true);
firebase.auth().signInWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onRegister = (email, password, repeatedPassword) => {
setIsLoading(true);
if (password !== repeatedPassword) {
setError("Error: Passwords do not match");
return;
}
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onLogout = () => {
setUser(null);
firebase.auth().signOut();
};
return (
<AuthenticationContext.Provider
value={{
isAuthenticated: !!user,
user,
isLoading,
error,
onLogin,
onRegister,
onLogout,
}}
>
{children}
</AuthenticationContext.Provider>
);
};

Firebase: using DocumentSnapshot id as User uid. Thoughts?

Currently users profiles are created by adding a user to a firestore collection.
I then have an onCreate function that will create a user in Firebase Authentication.
Would there be an issue with using the firestore docId as the created users uid?
Thanks
export const createUser = functions.region('europe-west2')
.firestore.document('users/{user}')
.onCreate(async (snap, context) =>
executeOnce(context.eventId, async (transaction: FirebaseFirestore.Transaction) => {
const data = snap.data();
await auth.createUser({
uid: snap.id,
email: data.email,
password: //,
displayName: `${data.firstName} ${data.lastName}`,
}).then(async function (newUser) {
return db.collection('users').doc(snap.id)
.update({
status: 'Active',
});
}).catch(function (error) {
return console.error(`Error creating user, ${error}`);
});
}));
I don't see why that would be an issue. Usually UID from Firebase Authentication is used as Firestore document ID. It's just an identifier so you can accurately point at the document containing current user's information. (<= just an example)
const uid = firebase.auth().currentUser.uid
const docRef = firebase.firestore().collection("users").doc(uid)
At the end, it's just a unique string. So as long as they both are same, you should be fine. Even if they are not, you could still query Firestore document using .where("userId", "==", uid).

Firebase.default.auth().currentUser.uid is null after createUserWithEmailAndPassword()

I'm trying to create a new document on my "users" collection on Firebase for any new user created on the Signup screen for my React-Native app, and the document is supposed to include the new user's uid, first name, last name, phone number, and date of birth. The issue is, after I use createUserWithEmailAndPassword to create a user, when I try to grab the uid with currentUser.uid, I get the following error: null is not an object (evaluating '_Firebase.default.auth().currentUser.uid').
I've been experimenting with ways to get the new user's "uid" in the .then statement following createUserWithEmailAndPassword and also creating the new document within the .then statement but I haven't gotten any luck with that yet. How should I modify my code so that I'm able to successfully create a new "users" document after successfully creating a user?
Below is my code from my handleSignUp function that is called when my "Sign Up" button is clicked:
handleSignUp = () => {
Firebase.auth()
.createUserWithEmailAndPassword(this.state.email, this.state.password)
.then(() => this.props.navigation.navigate("Main"))
.catch((error) => this.setState({ errorMessage: error.message }));
if (Firebase.auth().currentUser.uid) {
const user = {
uid: Firebase.auth().currentUser.uid,
firstName: this.state.firstName,
lastName: this.state.lastName,
phone: this.state.phone,
email: this.state.email,
dob: this.state.dob
};
db.collection("users").doc(response.user.uid).set(user);
}
};
If you want to:
Create a user
Write their details to the database
Navigate to a new page
You'll need to make sure you do these steps in that order, and use the promises returned by each step to make sure things happen at the right time:
Firebase.auth()
.createUserWithEmailAndPassword(this.state.email, this.state.password)
.then((credentials) => {
const user = {
uid: credentials.user.uid,
firstName: this.state.firstName,
lastName: this.state.lastName,
phone: this.state.phone,
email: this.state.email,
dob: this.state.dob
};
return db.collection("users").doc(response.user.uid).set(user);
}).then(() => {
this.props.navigation.navigate("Main")
}).catch((error) => this.setState({ errorMessage: error.message }));

Firebase set method not adding data to an existing Collection

I am using VUEX and Firebase to create a register form with three fields NAME, EMAIL, PASSWORD. First i am using createUserWithEmailAndPassword method to add the user but I also want to ad the name filed data to an Existing Blank collection, here I am using set method. But it is not adding the name field data in the collection.
methods: {
onSignUp() {
firebase
.auth()
.createUserWithEmailAndPassword(this.email, this.password)
.then(user => {
console.log(user);
console.log(user.user.uid);
firebase.database.collection("profiles").doc(user.user.id).set({
name: this.name
})
.then(function() {
console.log("Document successfully written!");
})
.catch(function(error) {
console.error("Error writing document: ", error);
});
this.$store.dispatch('signUserUp', user);
})
.catch(error => {
this.$store.dispatch('signUserError', error)
});
}
}
data(){
return {
name: "",
email: "",
password: "",
}
}
After submitting the form it's adding a new user and I can also see the uid in the console but some how its not updating the name field in the database.
You should use firebase.firestore() and not firebase.database (See this doc) and therefore adapt your code as follows:
onSignUp() {
firebase
.auth()
.createUserWithEmailAndPassword(this.email, this.password)
.then(user => {
console.log(user);
console.log(user.user.uid);
return firebase.firestore().collection("profiles").doc(user.user.id).set({
name: this.name
});
})
.then(() => {
console.log("Document successfully written!");
this.$store.dispatch('signUserUp', user);
})
.catch(error => {
this.$store.dispatch('signUserError', error)
});
}
}
You should also check that your security rules for the profiles collection are correctly set. Normally (authenticated) users should only be able to write a document with their own userid as document id.

Email/Password auth provider returns empty userRecord

I have a cloud function that is triggered by Auth user creation. I look up the user data (email, name, etc) to populate my DB. It suddenly stopped working for the 'email/password' Auth provider type. The admin.auth().getUser(uid) now returns a userRecord which contains undefined/null values for most fields. This seemingly stopped working out of nowhere in Production after functioning for several weeks, is there any possible explanation?
exports.createUser = functions.auth.user().onCreate((user) => {
return createEmailUser(user);
});
function createEmailUser(user) {
const uid = user.uid;
return admin.auth().getUser(uid)
.then(function(userRecord) {
console.log(userRecord);
const email = userRecord.email;
const fullName = userRecord.displayName;
admin.database().ref('users/' + uid).set({
email: email,
name: fullName
});
})
.catch(function(error) {
console.log("Error fetching user data:", error);
});
}
In the past, the userRecord object contains valid email and displayName values. Now, I see an object like this:
UserRecord {
uid: 'Lrtj8zafsnYjZl4ckMgwNkgEiVH2',
email: undefined,
emailVerified: false,
displayName: undefined,
photoURL: undefined,
phoneNumber: undefined,
disabled: false,
metadata:
UserMetadata {
creationTime: 'Wed, 09 Jan 2019 21:40:31 GMT',
lastSignInTime: null },
providerData: [],
passwordHash: undefined,
passwordSalt: undefined,
customClaims: undefined,
tokensValidAfterTime: 'Wed, 09 Jan 2019 21:40:31 GMT' }
As users are registered with Email/Password method, then there is only the email address available in userRecord. Other sign-in providers might have different data at user creation.
What you can do here is to check user data at profile creation, and update profile with updateUser if anything is missing:
function createEmailUser(user) {
const uid = user.uid;
admin.auth().updateUser(uid, {
phoneNumber: "+11234567890",
displayName: "Foo Bar"
})
.then(function(userRecord) {
console.log(userRecord);
})
.catch(function(error) {
console.log("Error fetching user data:", error);
});
}

Resources