I'm trying to learn about Cloud Functions for Firebase. I followed a tutorial to create an auth trigger and it worked great, but now I need to pass the username that the user wants to use to my auth event. How do I do this? Did I maybe use the wrong trigger here and instead needed an HTTP trigger?
exports.newUserSignup = functions.auth.user().onCreate(user => {
console.log('user created', user.email, user.uid);
const doc = admin.firestore().collection('users').doc();
return doc.set({
createDate: admin.firestore.FieldValue.serverTimestamp(),
modifiedDate: admin.firestore.FieldValue.serverTimestamp(),
username: 'NEED TO FIGURE THIS OUT',
email: user.email,
stat: 1,
uid: user.uid,
rowpointer: doc.id,
});
});
There is no way to pass additional information to the auth.user().onCreate trigger, and unfortunately there is no way to trigger when the account gets updated.
Your current options are:
Create the document from within your application code after the account has been created.
Pass all information to a Callable or HTTP Cloud Function, and let that handle both the account creation and the writing to Firestore.
Both are completely valid options, so pick the one that seems most reasonable to you.
Also see:
How to complete login only after functions.auth.user().onCreate is finished
Firebase Cloud Function - null user.displayName onCreate
firebase cloud function authentication: onCreate event doesn't contain displayName
Firebase functions user().onCreate: Pass parameters
Related
I have been using the new GIS library with the One Tap UX. I have followed all the steps outlined in the setup guide and the authentication works as expected, in fact I like the new flow; Nonetheless I am experiencing a very peculiar issue. For some reason, the picture property is missing from the credential response.
A couple of things to note are:
This is an internal application, which means it is only being used by the google workspace users of the respective domain.
This happens with all users whose profile picture is set using the Admin Directory API.
What I have in the front end is the following:
const promptParent = "signInBox";
const gsiInitializeConfig = {
client_id: '4xxyy55zzz.apps.googleusercontent.com',
callback: handleCredentialResponse,
prompt_parent_id: promptParent,
auto_select: true,
cancel_on_tap_outside: false
};
let idClient;
const doSignIn = ()=>{
idClient = google.accounts;
idClient.id.initialize(gsiInitializeConfig);
idClient.id.prompt();
}
const handleCredentialResponse = (response)=>{
const {credential} = response;
//send idToken to the backend for verification
});
When the DOMContent is loaded, I programatically invoke the doSignIn function. And the one tap shows and it works great. It returns the idToken which then I send to the backend for verification. In the backend I am using express and I have the following:
const token = getTokenFromBody();
if(!token){ return unauthenticated(res); }
const auth2Client = new OAuth2Client(GOOGLE_CLIENT_ID);
const ticket = await auth2Client.verifyIdToken({
audience: GOOGLE_CLIENT_ID,
idToken: token
}).catch(error=>{
console.error(`Failed to verify google id token: ${error}`);
unauthorized(res, "Invalid grant");
});
if(!ticket){ return; }
const ticketPayload = ticket.getPayload();
console.log("ticketPayload", JSON.stringify(ticketPayload));
The logged object for the ticketPayload above looks like this:
{
iss: "https://accounts.google.com",
nbf: 1378,
aud: "44xxyy55zz.apps.googleusercontent.com",
sub: "1559234417",
hd: "domain.com",
email: "user#domain.com",
email_verified: true,
azp: "44xxyy55zz.apps.googleusercontent.com",
name: "User Name",
given_name: "User",
family_name: "Name",
iat: 1664828678,
exp: 1664832278,
jti: "f0549d2544c905sadfcbc13110"
}
That is the response for all the users in the domain, however, for my user, whose photo was set using the page "https://myaccount.google.com", the response is the following:
{
iss: "https://accounts.google.com",
nbf: 1378,
aud: "44xxyy55zz.apps.googleusercontent.com",
sub: "1559234417",
hd: "domain.com",
email: "user#domain.com",
email_verified: true,
azp: "44xxyy55zz.apps.googleusercontent.com",
name: "User Name",
picture: "https://lh3.googleusercontent.com/a-/N5pZpE-zbJUg3=s96-c", // <-----!!!
given_name: "User",
family_name: "Name",
iat: 1664828678,
exp: 1664832278,
jti: "f0549d2544c905sadfcbc13110"
}
In comparisson with the old google sign in library, this behavior is different. How can I get the picture property for all users in the domain?
I reached out to Google Workspace support were they educated me with the following:
Admin-set user profile picutres (either set by an Admin via the Admin Console itself, or by the Admin SDK API call) are private and are not returned in the credential response of the Google Sign In flow.
The reason for that is that when an administrator sets a profile photo to a user's account, the photo becomes visible only to users in the organization and to external users they use Google Chat with. In contrast, (and only if users are allowed to manage their own profile photo) user-set photos are public by default. That is explained under the "Where a user's photo appears section of this support article.
This behavior cannot be changed for privacy reasons. An admin-set photo is neither public information, nor is set by the user, hence is not available in the token.
Although the solution provided makes sense, there is something that I feel is wrong. The fact that the old google sign in library never presented this behavior makes me feel that way. Why should the new sign in library present this behavior now? It is really absurd.
This code i used to store users in firebase.
this.db
.doc('/users/' + user.uid)
.set({
name: user.displayName,
email: user.email,
})
.then(() => console.log('user saved successfully'))
.catch((reason: any) => console.log('user save failed:', reason));
Users also have a isAdmin property that gets set elsewhere.
When I login as a new user, the user gets a name and email.
If i make the user admin i can visit admin only pages.
issue is, if i refresh on an admin page i get kicked of the page.
I think the issue is the set method, since it doesnt contain the isAdmin property
When I use update instead of set, it works fine when I refresh, but now I cant create new records for new users.
What is the best way to tackle this?
You're looking to merge the data in your set call with the existing data (if any), which you can do by specifying set options:
this.db
.doc('/users/' + user.uid)
.set({
name: user.displayName,
email: user.email,
}, { merge: true }) // 👈
Also see: https://firebase.google.com/docs/firestore/manage-data/add-data#set_a_document
I want to set up custom claims to a certain number of users let's say 5 users would be admins on my website. I want these 5 users to be able to log in through the login page which would redirect them to the dashboard.
but I still don't fully understand the concept of the custom claims and how to use them and firebase documentation is limited with examples.
In their example they show that I can pass a uid that I want to assign a custom claim to, but how is this supposed to be a variable when i want certain users uid's from my firestore database Users collection to be admins and have a custom claim, in other words, where would I put this code or how would I assign a custom claim to more than one user at a time and how and where would this code be executed.
if anyone can give me an example of how I would make this work.
here is what I did:
created a firebaseAdmin.js file:
var admin = require("firebase-admin");
// lets say for instance i want these two users to be admins
//2jfow4fd3H2ZqYLWZI2s1YdqOPB42
//2jfow4vad2ZqYLWZI2s1YdqOPB42 what am i supposed to do?
admin
.auth()
.setCustomUserClaims(uid, { admin: true })
.then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
I honestly don't know what to do from here.
Custom Claims can only be set from a privileged server environment via the Firebase Admin SDK. The easiest ways are either using a Node.js script (running the Admin SDK) or a Cloud Function (which also uses the Admin SDK).
Let's look at the example of a Callable Cloud Function that you call from your front-end (and in which you could check the UID of the user who is calling it, i.e. a Super Admin).
exports.setAdminClaims = functions.https.onCall(async (data, context) => {
// If necessary check the uid of the caller, via the context object
const adminUIDs = ['2jfow4fd3H2ZqYLWZI2s1YdqOPB42', '767fjdhshd3H2ZqYLWZI2suyyqOPB42'];
await Promise.all(adminUIDs.map(uid => admin.auth().setCustomUserClaims(uid, { admin: true })));
return { result: "Operation completed" }
});
A Node.js script would be similar:
#!/usr/bin/node
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert(".....json") // See remark on the private key below
});
const adminUIDs = ['2jfow4fd3H2ZqYLWZI2s1YdqOPB42', '767fjdhshd3H2ZqYLWZI2suyyqOPB42'];
Promise.all(adminUIDs.map(uid => admin.auth().setCustomUserClaims(uid, { admin: true })))
.then(() => {
console.log("Operation completed")
})
You must generate a private key file in JSON format for your service account , as detailed in the doc.
Then, when the Claims are set, you can access these Claims in your web app, and adapt the UI (or the navigation flow) based on the fact the user has (or not) the admin claim. More detail here in the doc.
Is it possible to update a user and the custom claims at the same time?
I can update the custom claims via the example in docs
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
});
I can update the user with
firebase.auth().updateUser(request.body.user.uid, {
displayName: request.body.user.username,
email: request.body.user.email
})
I thought it would be as simple as this example below but it keeps leaving the custom claims as undefined
const customClaims = {
admin: true,
accessLevel: 9
};
firebase.auth().updateUser(request.body.user.uid, {
displayName: request.body.user.username,
email: request.body.user.email,
customClaims: customClaims
})
Is there a way to do both at the same time? Or do they have to be separate?
The updateUser method is documented to take a string uid and an UpdateRequest object. As you can see from the linked API docs, UpdateRequest doesn't have a customClaims property, or anything that lets you update the claims in the same call. You can always file a feature request if this is important to your use case.
i am using Angular with firebase/firestore and i have an authentication system with the google oauth and everytime when i log in and try to create a document in firestore it deletes it afterwards apparently. The logs say:
#firebase/firestore: Firestore (5.0.3) [Connection]: WebChannel received: {"documentDelete":{"document":"projects/website/databases/(default)/documents/users/Tplww82foIN36hb8mcmSOaAPXbU2","readTime":"2018-05-21T05:35:42.653774Z","removedTargetIds":[2]}}
But i executed this code:
this.db.doc('users/' + user.uid).set({
uid: user.uid,
email: user.email,
name: user.displayName,
photoUrl: user.photoURL,
roles: {}
}, {merge: true});
Am i missing something obvious? Do i need to provide more logs/code in order to figure this out? Thank you very much :D
It was an easy solution actually: Every observable/promise takes their time, and i signed myself out before it was done creating the documents.