writing to firestore after authenticating - node.js

Im creating a react native application using expo and I'm currently trying to connect the firestore database to the application. When I run this in a function it doesn't write to the database but it does create a authenticated user.
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((result) => {
firebase
.firestore()
.collection('users')
.doc(result.user.uid)
.set({ uid: result.user.uid });
})
.catch((err) => console.log(err));
I found a way around this so that it does write to the database but wasn't sure if this is an acceptable way
firebase
.auth()
.signInWithCredential(credential)
.catch(function (error) {
console.log(error);
});
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log('adding to db');
firebase
.firestore()
.collection('users')
.doc(user.uid)
.set({ uid: user.uid });
} else {
// No user is signed in.
}
Any advice is appreciated !
Thank you

but wasn't sure if this is an acceptable way
This is not only acceptable, but it's currently the right way. The problem is that the Firebase Auth SDK doesn't set the current user before the promise resolves from the call to signInWithCredential. Without a current user set, the Firestore SDK doesn't know what credentials to use for a query. However, when onAuthStateChanged delivers a user object, you know for certain that the current is user is set.

Related

How to set custom auth claims through Firebase and identify platform

I am following the firebase documentation here to set custom auth claims for users logging into my app for the first time using firebase auth + identify platform but it does not seem to be working.
When a user logs in for the first time, I want them to get the admin custom claim. I have created the following blocking function and have verified from the logs that it runs when I log in for the first time to my app using sign-in with google:
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
return {
customClaims: {
admin: true,
},
};
});
I would expect this to create the admin custom claim in the user's token. However, when I get a list of claims using another cloud function the admin claim does not appear.
exports.getclaims = functions.https.onRequest(async (req, res) => {
const uid = req.query.uid as string;
if (uid) {
const user = await admin.auth().getUser(uid);
res.send(user.customClaims);
} else {
res.sendStatus(500);
}
});
If I set the claim using the admin SDK directly using the below cloud function, the admin claim does appear.
exports.setclaim = functions.https.onRequest(async (req, res) => {
const uid = req.query.uid as string;
if (uid) {
await admin.auth().setCustomUserClaims(uid, {admin: true});
res.sendStatus(200);
} else {
res.sendStatus(500);
}
});
What am I doing wrong in the beforeCreate function?
There's an open GitHub issue regarding that. See sessionClaims content not getting added to the decoded token. Also, there's a fix that has been recently merged regarding this issue.
From the snippet you provided, there does not appear to be anything wrong with beforeCreate as coded.
You may want to check you do not have a beforeSignIn that is overwriting the customClaims directly or via sessionClaims.
https://firebase.google.com/docs/auth/extend-with-blocking-functions#modifying_a_user
Try to use onCreate method instead of beforeCreate how it is shown on the official docs
functions.auth.user().onCreate(async (user) => {
try {
// Set custom user claims on this newly created user.
await getAuth().setCustomUserClaims(user.uid, {admin: true});
// Update real-time database to notify client to force refresh.
const metadataRef = getDatabase().ref('metadata/' + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
await metadataRef.set({refreshTime: new Date().getTime()});
} catch (error) {
console.log(error);
}
}
});
The main point here is that you need to create the user at first and then update claims and make the force update of the token at the client side:
firebase.auth().currentUser.getIdToken(true);

Why is 'currentUser' and 'onAuthStateChanged' in firebase always null?

What I want to achieve
A user, who logged in or signed up should not re-login after one hour. The restriction of one hour comes from firebase authentication, if not prevented (what I try to accomplish).
Problem
After a user is logged in via firebase authentication (signInWithEmailAndPassword) I always get null for currentUser and onAuthStateChanged.
What I tried
I'm using React (v17.0.2) using 'Create React App'. On server side I'm using NodeJS (v12). The communication between both is accomplished using axios (v0.21.1)
First I tried to send the token stored in localStorage, which came from firebase (server side), back to the server. But the server tells me, that the token is no longer valid. Server side code as follows:
module.exports = (request, response, next) => {
let idToken;
if (request.headers.authorization && request.headers.authorization.startsWith('Bearer ')) {
idToken = request.headers.authorization.split('Bearer ')[1];
console.log("idToken:", idToken);
} else {
console.error('No token found');
return response.status(403).json({ error: 'Unauthorized' });
}
admin
.auth()
.verifyIdToken(idToken)
.then((decodedToken) => {
console.log('decodedToken', decodedToken);
request.user = decodedToken;
return db.collection('users').where('userId', '==', request.user.uid).limit(1).get();
})
.catch((err) => {
console.error('Error while verifying token', err);
return response.status(403).json(err);
});
};
After that I tried the following code on client side.
handleSubmit = () => {
const userData = {
email: this.state.email,
password: this.state.password
};
axios
.post(firestoreUrl() + '/login', userData)
.then((resp) => {
console.log("token:", resp.data); //here I get a valid token
localStorage.setItem('AuthToken', `Bearer ${resp.data.token}`);
console.log("firebase.auth().currentUser:", firebase.auth().currentUser); //but this is null
})
.then((data) => {
console.log(data);
console.log("firebase.auth().currentUser:", firebase.auth().currentUser); //still null
})
.catch((error) => {
console.log("Error:", error);
});
};
What irritates me is that I get a token from firebase (server side), the token is then stored in localStorage (client side) but firebase then tells me, that the currentUser is null. But presumably they are not mutually dependent =/.
I'm able to access all secured sites in my app. I can log out and in again. But whatever I do the currentUser is null.
I also tried to run the code above in componentDidMount()-method. But no success.
I tried an approach from this link (hopefully in a way it should be), but it didn't work. Still getting null for both currentUser and onAuthStateChanged if I implement following code.
firebase.auth().onAuthStateChanged(user => {
if (user) {
console.log("state = definitely signed in")
}
else {
console.log("state = definitely signed out")
}
})
I always get logged to the console, that the user is 'definitely signed out'.
During research I noticed that the point at which I should try to get the currentUser-Status is kind of tricky. So I guess that one solution is to implement the currentUser-code at another/the right place. And here I'm struggling =/.
As I found out at a similar question here on SO, I did a bad mistake. Apparently, it's not a good idea to perform the signIn- or createUser-functionality on server side. This should be done on client side. In the question mentioned above are some good reasons for doing that on server side but in my case it's quite ok to run it on client side.
Thanks to Frank van Puffelen for leading the way (see one of the comments in the question mentioned above).

How to get an idToken that is valid for development from firebase without having to spin up my frontend?

I am working on some firebase functions. This one will check if an user is logged in in firebase first. However this is a bit of a hassle in development. Now I need to login on the frontend first to get the id_token, pass it to my function url, then see the result.
The process I am following is described in the official docs: https://firebase.google.com/docs/auth/admin/verify-id-tokens
node.js
const admin = require('firebase-admin');
admin.initializeApp();
module.exports = function( request, response ) {
if( !request.query.id_token )
response.status(400).json({message: 'id token has not been provided'});
admin.auth()
.verifyIdToken( request.query.id_token )
.then( token => {
// TODO: redirect to payment portal
return response.status(200).json({message: 'Success'});
})
.catch( error => {
return response.status(401).json({message: 'You are currently not logged in as an authorised user'});
})
}
Is there a way to get an id_token that is valid from firebase without having to spin up my frontend? Good and simple alternatives solutions are welcome too.
NOTE: I am using the firebase emulators during development.
Since you're using the Firebase emulators you may create a fake user and retrieve an id token programmatically. The code below creates and logs in a user and returns an id_token that will be accepted by your function.
var firebase = require("firebase/app");
require("firebase/auth");
// Initialize Firebase and connect to the Authentication emulator
var firebaseConfig = {
// Insert Firebase config here
};
firebase.initializeApp(firebaseConfig);
firebase.auth().useEmulator('http://localhost:9099/');
// Create a fake user and get the token
firebase.auth().createUserWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User created")
});
firebase.auth().signInWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User logged in")
userCredential.user.getIdToken().then((idToken) => {
console.log(idToken)
});
});

Cloud function trigger on document update

I am trying to fire my cloud function if theres a document update on users/{userId}
I have a signIn method that's fired everytime a user logs in
signin: (email, password, setErrors) => {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
const isVerified = firebase.auth().currentUser.emailVerified
const userUid = firebase.auth().currentUser.uid
const db = firebase.firestore()
if (isVerified) {
db.collection('/users')
.doc(userUid)
.update({ isVerified: true })
}
})
.catch(err => {
setErrors(prev => [...prev, err.message])
})
},
Nothing fancy, aside from the basic log in stuff, it also checks if the user has verified their email, if it is verified it will update the collection for that user. Everything is working as intended here.
However I can't seem to get my cloud function to fire.
Basically, it's listening for changes on ther user collection. If users/{userId} document has isVerified and the user email address ends with xxxx it should grant them admin privledges.
exports.updateUser = functions.firestore.document('users/{userId}').onUpdate((change, context) => {
const after = change.after.data()
if (after.isVerified) {
firebase.auth().onAuthStateChanged(user => {
if (user.emailVerified && user.email.endsWith('#xxxx')) {
const customClaims = {
admin: true,
}
return admin
.auth()
.setCustomUserClaims(user.uid, customClaims)
.then(() => {
console.log('Cloud function fired')
const metadataRef = admin.database().ref('metadata/' + user.uid)
return metadataRef.set({ refreshTime: new Date().getTime() })
})
.catch(error => {
console.log(error)
})
}
})
}
})
Right now the function is not firing, any ideas?
Code in Cloud Functions runs as an administrative account, which cannot be retrieved with the regular Firebase Authentication SDK. In fact, you should only be using Firebase Admin SDKs in your Cloud Functions code, in which the onAuthStateChanged method doesn't exist.
It's not entirely clear what you want this code to do with the user, but if you want to check whether the user whose uid is in the path has a verified email address, you can load that user by their UID with the Admin SDK.
To ensure that the uid in the path is the real UID of the user who performed the operation you can use security rules, as shown in the documentation on securing user data.
I support what Frank said.
You can fetch users with admin in this way:
if (after.isVerified) {
admin.auth().getUser(userId)
.then((userData) => { ...

Android Firebase cloud function - delete user using email

i'm trying to delete a user from firebase auth using a cloud function which is triggered when i delete the document of the user. this is a workaround to enable one client "admin" permissions to delete other users without admin SDK. in the document i store the users email. how do i delete the user from auth using the email?
it should be something like this:
exports.sendDelVolunteer = functions.firestore.document('Users/{messageId}').onDelete((snap, context) => {
const doc = snap.data();
const user = admin.auth().getUserByEmail(doc.email).then(function(userRecord) {
return admin.auth().deleteUser(userRecord.uid).then(function() {
console.log('Successfully deleted user');
})
.catch(function(error) {
console.log('Error deleting user:', error);
});
}).catch(function(error) {
console.log('Error fetching user data:', error);
});
});
currently i get the following error: "error Each then() should return a value or throw"
thanks!!!
enter image description here
You're missing a top-level return statement.
exports.sendDelVolunteer = functions.firestore.document('Users/{messageId}').onDelete((snap, context) => {
const doc = snap.data();
return admin.auth().getUserByEmail(doc.email).then(function(userRecord) {
return admin.auth().deleteUser(userRecord.uid)
});
});
Aside from that this looks like a good approach. Just make sure that only your administrator(s( can delete these documents, as otherwise you still have a security hole.

Resources