Firebase auth + firestore (what is the proper way to "link" users) - javascript

I'm making smth with backed for the first time in my life, so I'm sorry in advance, I'm making a web chat app. I think I managed to deal with authentication (it seems to be working) and now I want to make connect somehow the authentication user names with chat users... so I tried to
const docRef = await addDoc(collection(database, 'users'), {
name: user.displayName,
});
console.log('Document written with ID: ', docRef.id);
} catch (e) {
console.error('Error adding document: ', e);
}
but it says user not defined because userCredentials is in the scope of authentications functions...
If I paste this code into some function where userCredentials can be found, it says there is some problem with await word...
I want to take userCredential that logged in and use it in the chat app... so I need to link somehow the auth db and the firestore db? or is it done completely differently? Thank you
Could you give a bit of advice? Thank you (edited)

If you want to use the user's name, you first need to make sure that your code only runs once the user is signed in. In Firebase you can do that as shown in the documentation on getting the currently signed in user:
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// 👇 your application specific code is below
const docRef = await addDoc(collection(database, 'users'), {
name: user.displayName,
});
console.log('Document written with ID: ', docRef.id);
} else {
// User is signed out
// ...
}
})
I'd actually recommend not using addDoc, but basing the document ID on the UID of the user, like this:
if (user) {
// 👇 get UID from user
const uid = user.uid;
// Use as document ID 👇 👇 👇
const docRef = await setDoc(doc(database, 'users', uid), {
name: user.displayName,
});
Since document IDs are unique within a collection, this automatically ensures that each user can only have one document in the users collection. It also makes it easier to find a user's document when needed, and makes it easier to ensure users can only edit their own document.

Related

How do I fetch user information to display in a feed of posts using firebase storage solutions?

I'm building a forum-style application where users post content that displays on a global feed. I want to display information about the user in posts (photoURL, displayName) similar to Twitter.
I have firebase v9 using the authentication and firestore for the posts. The reason I want to reference the auth is that I can catch changes to the user's information as it happens, this way the feed is up to date.
I save the user's unique ID with the post so I am able to reference who to display. I can successfully reference the post title and description with doc.title & doc.description however I get stuck when retrieving user information. I'm trying doc.UserID.displayName for the display name but I know this is incorrect. I can't find anything in the docs for this specific use case, is this something that I can do with just firestore and auth?
Do I need to create a reference to the auth storage with doc.UserID?
Here is the code:
// add a new post
addPostForm.addEventListener('submit', (e) => {
e.preventDefault();
onAuthStateChanged(auth, (user) => {
const colRef = collection(db, 'Posts');
console.log(hiddenURL.value);
addDoc(colRef, {
UserID: user.uid,
beatURL: hiddenURL.value,
title: addPostForm.postTitle.value,
description: addPostForm.postDescription.value,
})
.then(() => {
console.log("Document written with ID: ", doc.id);
addPostModal.classList.remove('open');
addPostForm.querySelector('.error').textContent = "";
})
.catch(error => {
addPostForm.querySelector('.error').textContent = error.message;
alert(error);
})
})
});
export const initApp = async () => {
initFirebaseAuth;
const posts = await collection(db, 'Posts');
// render data to the page
return renderPosts(posts);
};
const renderPosts = (posts) => {
const main = document.getElementById("feed");
onSnapshot(posts, (snapshot) => {
let cardsArray = []
snapshot.docs.forEach((doc, user) => {
cardsArray.push({ ...doc.data(), id: doc.id })
name.textContent = `${doc.UserID.displayName}`; // users display name
avatar.src = doc.UserID.photoURL; //user's image
description.textContent = `${post.description}`;
title.textContent = `${post.title}`;
});
console.log(cardsArray);
});
};
There are two cases and approaches at first sight:
1. Your users profiles are only available in the Auth Service
In this case, via the JS SDK, a user X cannot "query" the Auth profile of a user Y.
This means that you need to save the author's displayName together with the author uid when the post is created.
2. Your users profiles are also available in a users collection (a common pattern)
In this case, when you display a post, you could fetch the user's document to get the author's displayName.
However, in the NoSQL world, you should not be afraid to duplicate data and denormalize your data model. When designing your data-model you should think about it from a query perspective, trying to minimize the number of queries for a given screen/use case. So approach #1 is recommended, even if you maintain a user's collection.
In case of changes in the user's profile, in order to synchronyse the post documents and user's data a common approach is to use a set of Cloud Functions (which are executed in the back-end) to update the post documents. The link between the posts and the users profile being the user's uid.

currentUser not work or not exist in firebase 9

import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
const user = auth.currentUser;
user return null !!
and this not
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;
console.log(user.email);
// ...
} else {
// User is signed out
// ...
}
});
What you're seeing seems like the expected behavior to me.
When you access auth.currentUser, it gives you the current user when that code runs. Firebase automatically restores the user's credentials when a page loads and this requires a call to the server. This means that when your auth.currentUser runs, that call may not have completed yet, and thus the current user is null.
When on the other hand you use onAuthStateChanged, Firebase only calls your code when it has completed the first check of the user's credentials. So now your code runs after the user has been signed in, and user is no longer null.
This is one of the main reasons it's almost always better to use an auth state listener.
You could use either of these:
this.user$ = authState(auth);
OR
this.user$ = new Observable((observer: any) =>
onAuthStateChanged(auth, observer)
);
OR
this.user$ = user(auth);
and get the Promise version like so:
const user = await this.user$.pipe(take(1)).toPromise();

Firebase Auth setCustomClaims() not working

I am facing a problem with setting custom claims for Firebase Authentication service's token. I am using Cloud function to set the custom claims for Hasura. The cloud function executes upon new user create event to set the custom claims. Here's my code running in cloud function
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.processSignup = functions.auth.user().onCreate(user => {
// create custom claims for hasura
const hasuraClaims = {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user"],
"x-hasura-user-id": user.uid
}
// attach claims to user auth object
return admin.auth().setCustomUserClaims(user.uid, hasuraClaims)
.then(_ => {
functions.logger.info('SUCCESS: Custom claims attached');
})
.catch(err => {
console.log('ERROR: ', err);
})
})
In my frontend web page, I am running the following code to get the idToken
// subscribe to user state change
firebase.auth().onAuthStateChanged(async user => {
console.log('Firebase auth state changed');
if (user) {
// User is signed in.
window.User = user;
let idToken = await user.getIdTokenResult();
console.log('idToken: ', idToken);
}
})
I don't know what I'm doing wrong, but the token doesn't contain the custom claims that I've set in my Cloud function processSignup(). I know that the function executed without error because I can check my function logs and find the info entry SUCCESS: Custom claims attached.
Can anyone please help me solve this problem?
Updating claims does not trigger an onAuthStateChanged (the auth state of being logged in or not has not changed, but the users' claims have) and tokens are minted and then used for ~1h.
You are calling getIdTokenResult but not forcing a refresh, try:
let idToken = await user.getIdTokenResult(true);
which will force a new token to be fetched from the server and will (hopefully) include your custom claims.

Amplify w/Cognito: How do I get the Current User's Email?

I am using AWS Amplify, with Cognito for user Auth.
Users go into a user pool, and register and sign in just with email address and password.
When a user that has signed in through Cognito navigates to a certain page, I want to be retrieve their email address. How can I do this?
I am able to retrieve some user data with this code (I am using javascript/Angular):
import Auth from '#aws-amplify/auth';
...
ngOnInit(){
Auth.currentAuthenticatedUser().then((user)=>{
console.log('user = ' + JSON.stringify(user.pool))
})
}
The email does appear on the response, but I haven't yet been able to isolate the email from the returned JSON.
I've tried going through the docs, but I haven't yet found info on stuff like the attribute options I can add to currentAuthenticatedUser(), or if there is another method that is cleaner (which I assume there is).
EDIT: It looks like the following works:
Auth.currentAuthenticatedUser().then((user) => {
console.log('user email = ' + user.attributes.email);
});
But I am still hoping to understand the documentation better. I found this solution in a random github question, not the official docs. Where would I find this solution in the AWS Amplify / Cognito documentation?
import cognito from "../path/to/your/config/cognito.json";
import Amplify, { Auth, Hub } from 'aws-amplify';
...
...
useEffect(() => {
Amplify.configure({ Auth: cognito });
Hub.listen('auth', ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
console.log('Event name -> ', event, data)
// here is your name, email e.t.c.
console.log(data.signInUserSession.idToken.payload);
break
case 'signOut':
console.log('sign out')
// this.setState({ user: null })
break
default:
console.log('Unhandled use case - ' + event)
}
})
}, [])
You can check this one from the official documentation
and enable read access
General settings -> App clients -> Show details -> Set attribute read and write permissions link
and then to make sure you are fetching the updated attributes
Auth.currentAuthenticatedUser({ bypassCache: true })
Auth.currentSession()
.then((data) => {
// this data has user details in accessToken
}).catch(err => console.log(err));
The following works for me after the user is logged in...
import { Auth, Amplify } from 'aws-amplify'
console.log(Auth.user.attributes.email)

Check for firebase's auth user's role when or after logging in

My firebase app has two different roles: user and admin. I assign these during the creation, which is done as follows:
const admin = require('firebase-admin')
...
const user = await admin.auth().createUser({
email,
emailVerified: true,
password,
displayName: name,
disabled: false
})
await admin.auth().setCustomUserClaims(user.uid, { role: 'user' })
For creating the admin we obviously do the same, but the last line becomes as follows:
await admin.auth().setCustomUserClaims(user.uid, { role: 'admin' })
These separate roles are use in the firebase rules to keep users from accessing certain collections as well as some cloud functions to prevent them from doing certain operations.
What I would like to do is on my client app to limit access to certain sections by checking the user role.
As it stands, when I authenticate I do not have access to the user role, so I don't know how to limit their access based on their role. Here is my authentication code:
// authenticating a user
const handle = firebase.auth().onAuthStateChanged(user => {
console.log('Authenticated user', user)
// do stuff
})
The issue here is that at this point the user object given to me by onAuthStateChanged doesn't have the role.
From the providerData attribute all I have is the following:
displayName
email
phoneNumber
photoURL
providerId
uid
The question is how can I access the user role on the client app to be able to block certain types of users from accessing restricted parts of the client app?
Try the following snippet:
firebase.auth().currentUser.getIdTokenResult()
.then((idTokenResult) => {
// Confirm the user is an Admin.
if (!!idTokenResult.claims.admin) {
// Show admin UI.
showAdminUI();
} else {
// Show regular user UI.
showRegularUI();
}
})
.catch((error) => {
console.log(error);
});
Source:
Firebase Auth

Categories

Resources