Firebase: use updated profile with onAuthStateChanged() - javascript

I have a little twisted problem with my React app. I'm using Firebase for authentication. What I need to do is to register a user and set their displayName immediately. Entire problem could be avoided if Firebase allowed to call createUserWithEmailAndPassword() without signing user in, but it doesn't.
I do this:
const submitHandler = async event => {
event.preventDefault();
const auth = await firebase.auth();
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await user.updateProfile({
displayName: userName
});
};
The problem is that I'm using firebase.auth().onAuthStateChanged() in my root component to handle current user state, so it's called as soon as createUserWithEmailAndPassword() is done, before the profile is updated. I'm not sure how to tell onAuthStateChanged() about the updated displayName.
I managed to create a working solution, but I'm not happy with it. I tricked onAuthStateChanged() to ignore users without displayName like so:
useEffect(() => {
const unsubscribeAuth = firebase.auth().onAuthStateChanged((user) => {
if (user === null || user.displayName !== null) {
setLoggedInUser(user);
}
});
return () => unsubscribeAuth();
}, []);
And in my sign up component, I do this:
const submitHandler = async event => {
event.preventDefault();
const auth = await firebase.auth();
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await user.updateProfile({
displayName: userName
});
await auth.signOut();
await auth.updateCurrentUser(user);
};
That's obviously hackish and slower than necessary, because I'm signing in twice. It works as intended though, ignoring the auto-login, then signing out (doing nothing, as the user state hasn't been updated before) and then signing in, triggering onAuthStateChanged() with the updated user.
How can I improve my app to sign up users without this double madness?

Updating the user profile not normally execute the onAuthStateChange listener, as (usually) the authentication state won't change because of this API call.
You can force a reload of the user's ID token to get their latest profile information.

Related

Trying to block authentication if user is in the banned collection

I am building a chat app and I am trying to figure out the banning system. The idea if the user is in the banned collection becaused he used banned words, block authentication from his account. The problem is that for some reason it does execute the code but rather only does the signInWithRedirect() function. Take a look at my code:
const googleProvider = new GoogleAuthProvider();
const signInWithGoogle = async () => {
try {
const res = await signInWithRedirect(auth, googleProvider);
const user = res.user;
const q = query(collection(db, "banned"), where("uid", "==", user.uid));
const docs = await getDocs(q);
if (docs.exists()) {
//if exists ban the user
console.log('you cannot use this chat app , you are banned!)
Here is also the LoginPage.jsx
function LoginPage() {
const [user] = useAuthState(auth)
const navigate = useNavigate()
console.log(user)
return (
<div>
<div style={{'display': 'none'}}>
<Navbar />
</div>
<Welcome />
</div>
)
}
Here is also signOut
const navigate = useNavigate()
const signOutWithGoogle = () => {
signOut(auth)
navigate('/')
}
Here is firebase firestore
I tried this with promises but nothing happened , I used async , nothing happened
I would not implement such a "banning" system only based on front-end code. It would actually be quite easy for a malicious end-user to bypass your check based on a standard query to a collection.
I would use a Firebase Security Rule to enforce the banishment: Security Rules stand between your data and malicious users and are executed/checked in the back-end. To be complete, with this approach the user will still be authenticated and signed-in but will not be able to interact with the Firebase back-end services like Firestore or Cloud Storage.
Concretely you could as follows:
Write a security rule based on a specific collection in which you create a document per banned user with the user ID as document ID. Then you can use the exists() method to check if the user is banned or not.
Use a Cloud Function to ban (and maybe "un-ban") a user. Again, with Cloud Function the code is executed in the back-end and you therefore avoid the possibility that a malicious user writes a code that could "un-ban" him.

How can i seperate two types of users in firebase to seperate the login of web and mobile app users

I am currently working on a project where the parking lot owners should use website login and other users should use mobile app login.
But currently any user can login into both of the website and mobile app.
here is my firebase realtime database
my realtime database
So as you can see I defined type in user. when signing up a user gets a type depending on the device he/she registering
and my web sign in function is like this:
signInWithEmailAndPassword(auth, email, password).then((userCredential) => {
const user = userCredential.user;
alert('User Logged in!');
window.location = 'user.html';
}).catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
alert(errorMessage);
});
How can I provide login for the users which have 'type = web' ?
Firebase Authentication only cares about credentials: if the email/password you enter matches the data in the system, you can sign in - no matter what platform you're on. There is no way to change this in Firebase Authentication, so any additional logic will have to come from your application code.
For example, you could store a list of the UIDs of the parking lot owners, and check against that after signing in to allow then to use the web app or not.
signInWithEmailAndPassword(auth, email, password).then((userCredential) => {
const user = userCredential.user;
if (user) {
const uid = user.uid; // determine the UID of the user
const ownersRef = firebase.database().ref("parkinglotOwners");
const userSnapshot = await ownersRef.child(uid).get(); // try to load this users data from parkinglotOwners
if (userSnapshot.exists()) { // if this data exists
window.location = 'user.html'; // send them to the web app
} else {
alert("You're not allowed to use this app"; // tell them to go away
}
}
...
}).catch((error) => {
Firebase Auth is about authentication (are you the person you said you are).
Your need is more about Access Control. There is a feature in Firebase that may help with that. It's called "custom claims" and allow you to perform Claim-Based Access control.
see this video: https://www.youtube.com/watch?v=3hj_r_N0qMs

A user doesn't have any persmission when its account has just been created in Firebase (web version), why?

I'm trying to use Firebase in the web project (with NextJS), but here is my problem: the user doesn't have an account, so naturally the account is created with createUserWithEmailAndPassword(). However, I want to store data in Firestore right after the creation of the account... and there is the problem. Indeed Firebase throws the following error:
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Here is an extract of my code:
import { useState } from "react";
import {
createUserWithEmailAndPassword,
getAuth,
} from "firebase/auth";
import {
collection,
getFirestore,
addDoc,
} from "firebase/firestore";
export const ConnectionPage = () => {
// the values of the inputs in the HTML content
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
// the function executed when the user clicks the register button
const submit = async (e) => {
e.preventDefault();
// create a new user with email and password
// and add the document in the "users" collection
try {
const userCredentials = await createUserWithEmailAndPassword(
getAuth(),
email,
password
);
const user = userCredentials.user;
const userInfo = {
uid: user.uid,
// ...and all kind of data
};
const collectionRef = collection(getFirestore(), "users");
// my problem starts here
// the following line will throw an error
// "[code=permission-denied]: Missing or insufficient permissions."
// whereas, according to the rules in Firestore, it should work
// because I just ask the user to be authenticated (`allow write, read: if request.auth != null`)
// and according to the documentation, `createUserWithEmailAndPassword` logs in the user.
const docRef = await addDoc(collectionRef, userInfo);
console.log(`New document with id '${docRef.id}' created successfully.`);
} catch (e) {
console.error("An error has occured during register, look:");
console.error(e.toString());
}
};
// returns a form
// with an email input
// and a password input
// and a button to register
};
Here are my rules in Firestore:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write, read: if request.auth != null;
}
}
}
Please help me, the only solution I can find on a similar problem is to sign out the user after the creation of the account, and sign in right after that.
I tried:
in-promise syntax (.then()) but the problem is the same than with async/await
onAuthStateChanged(), but the same problem occurs.
Changing the rules to something more complex (like testing the existence of the user according to the request.auth.uid) doesn't work either.
When you just have:
allow read;
This is an entire statement, and means that nobody can read the data.
If you want to apply the same condition for both read and write, use:
allow read, write: if request.auth != null;
Try using an auth state observer instead of just await on the creation of the account. In my experience, the current user object is not immediately set after account creation, and you have to wait until the auth state observer triggers with a non-null user object in order to be able to perform authenticated queries. Without a non-null currentUser, Firestore queries that require auth will fail.
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
If this feels like a bug to you, then I suggest filing it on GitHub.
the problem is fixed thanks to the contributors I talked to in GitHub. The problem was coming from the App Check feature which does not work with Firestore until the version 9.6.0.
The solution is:
npm install firebase#9.6.0
By default, npm install firebase is installing version 9.5.0.
Note that this version is recent and if you see that message in 2031 it's completely useless.
Thank to the people who answered me :)

Issues With Persistent React Native Login

I am trying to make a persistent login system with React Native and expo but am running into several issues. I read online that AsyncStorage is the way to do this. I wrote three functions for dealing with login, all seen below.
// use asyncstorage to log in the user
logInUser = async (uid) => await AsyncStorage.setItem('loggedin', uid)
// set loggedin to null in asyncstorage
logOutUser = async() => await AsyncStorage.setItem('loggedin', null)
// returns userid if user is logged in
getUserState = async() => await AsyncStorage.getItem('loggedin')
On my login screen, I use the following onPress event to log in the user.
onPress={() => {
db.logInUser(this.user[1]).then(() => {
//this.removekey(this.user[1]) // delete the user's one-time-login key
this.props.navigation.navigate('Home') // navigate to home
})
}}
Also on the login screen, I use the following componentDidMount function to send the user to the homescreen if they are already logged in.
async componentDidMount() {
db.getUserState().then(loggedin => {
if (loggedin != null) { // log the user in if they have a uid in asyncstorage
this.props.navigation.navigate('Home')
}
})
}
The app will not have a "logout" feature, and users should stay logged in until either buying a new phone or reinstalling the app. Unfortunately this code does not work, and actually automatically logs the user in. I was thinking that it could maybe relate to the user with id=0, but removing this user from the database had no effect. The code of the actual application is here.

How to delete user with Vue.js Firebase UID

I am attempting to implement a function in my Vue.js Firebase app that deletes/removes users by UID. The app is set up to enable registration of users in Firebase authentication via email and password. Once logged in, the user should be able to click delete in order to remove his/her email and password from Firebase authentication, as well as user data. So far I have the following for the delete function:
async deleteProfile () {
let ref = db.collection('users')
let user = await ref.where('user_id', '==',firebase.auth().currentUser.uid).get()
user.delete()
}
...but I am getting user.delete() is not a function. How can I set up this function to delete the user in authentication and database? Thanks!
UPDATED FUNCTION
async deleteProfile () {
let ref = db.collection('users')
let user = await ref.where('user_id', '==', firebase.auth().currentUser.uid).get()
await user.ref.delete()
await firebase.auth().currentUser.delete()
}
In your code, user is a DocumentSnapshot type object. If you want to delete the document, you can use user.ref.delete(). It returns a promise, so you will need to await it.
Deleting a document will not also delete a user account in Firebase Authentication. If you want to delete the user account, you will have to use the Firebase Authentication API for that. firebase.auth().currentUser.delete().
try this
<button class="..." #click="deleteProfile(currentUser.uid)">Delete</button>
and
methods: {
async deleteProfile(dataId) {
fireDb.collection("yourCollection").doc(dataId).delete()
.then(() => {
alert('Deleted')
})
}
}
Building off Doug Stevenson's answer, this is the function that ultimately worked.
async deleteProfile (user) {
await db.collection("users").doc(user).delete()
await firebase.auth().currentUser.delete()
}
await db.collection("users").doc(user).delete() accepts "user" as an argument from the click event in the DOM, in order to target the doc of the specified user in the database (I don't know why I didn't think of that sooner!) await firebase.auth().currentUser.delete() removes currentUser from firebase authorization.

Categories