react firebase auth displayName return null - javascript

Im trying to do firebase auth in conformity with google documentation. After user is registered he is redirecting to '/' path, then im getting user display name by onAuthStateChangedListener but it return null, although im checking full user data file and contains displayName field.
auth code
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((result) => {
result.user
.updateProfile({
displayName: nickname,
})
getting user data code
firebase.auth().onAuthStateChanged((data) => {
if (data) setUser(data);
console.log(data);
});
screen with contains display name

This is the expected behavior: the auth state changes on .createUserWithEmailAndPassword(email, password). Once that happened, the call to result.user.updateProfile(...) does not change the authentication state, so does not cause the auth state change listener to be called again.
If you want to ensure you have the latest auth profile, refresh the ID token after updating the profile by calling user.getIdToken(true) or user.reload(). But neither of these will cause an auth state change, so they won't cause calls to your auth state change handler either.
Instead: get the current user after these calls with firebase.auth().currentUser, and pass that to setUser()`.

Related

Firebase Web SDK: refreshing auth token so that email_verified is updated in firestore rules

Using Firebase Web SDK, I'm requiring users to verify their email before accessing Firestore documents. I have a Firestore rule that gates the document like this:
allow read: if request.auth != null && request.auth.token.email_verified;
I'd like the email verification to be reflected as soon as the user verifies his/her email without requiring the user to sign out and sign back in. Unfortunately onAuthStateChanged() doesn't fire when emailVerified changes, so I'm refreshing the client token by polling for changes to emailVerified. Something like this:
Note: My examples use the new beta Firebase Web SDK V9 (Modular Web SDK) in case the syntax is unfamiliar.
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
});
}, 2000);
With the code above, I can get emailVerified to be reflected property inside my web app, but the problem arises when I try to make a request to Firestore:
const unsubscribe = onSnapshot(
doc(db, 'widgets', 'widget1'),
snap => {
console.log(snap);
},
);
That request results in a Firestore permission error. Once the user signs out and signs back in, the Firestore request is accepted.
How can I get the auth token that gets sent to Firestore to be updated with the latest email_verified without the user to sign out and and sign back in?
It turns out that a series of steps need to happen to refresh the token. After email verification, you need to reload the user AND explicitly get a new id token with getIdToken(user, true) after you reload the user. Only after those 2 steps will an updated token be sent to Firestore for queries. You also need to unsubscribe and re-subscribe to onAuthStateChanged manually, as that doesn't get triggered on token change. The modified version of my example is:
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
getIdToken(auth.currentUser, true).then(() => {
// now the new token will be sent to Firestore, yay!
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
})
});
}, 2000);
Please post your answer if there's an easier way. I especially don't like the polling part.

Firebase authentication in ReactJS project - user null inside onAuthStateChanged

I'm tearing my hair out trying to setup email/password authentication through firebase.
I've got my firebase configuration setup like so
// Firebase App (the core Firebase SDK) is always required and must be listed first
import firebase from "firebase/app";
//authenticaion module
import "firebase/auth";
// Add the Firebase products that you want to use
import "firebase/firestore";
var firebaseConfig = {
// I've got my api key and other info copied from the console in here
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
export const db = firebase.firestore();
export const app = firebase.auth();
I've got a sign-in form created that calls the following function:
import { app } from "../firebase/firebaseConfig";
const createAccount = () => {
app
.createUserWithEmailAndPassword(email, password)
.then((user) => {
console.log("user created");
console.dir(user);
})
.catch((error) => {
console.log("something went wrong");
});
};
First problem: I'm not seeing the "user created" message, even though I can see the user being created in the Firebase Authentication console. I'm also not seeing the "something went wrong" message that would indicate an exception occurred.
Second problem: I'm trying to do a re-direct when the user signs in. To do this, I've setup a listener on the Firebase auth object as suggested in the docs:
firebase.auth().onAuthStateChanged((user) => {
console.log("Inside listener");
console.dir(user);
});
The problem is, I'm seeing the console message so the function is triggering but 'user' is always null in here even though the user is being created.
Firebase version: 8.2.1
Can anyone see what I'm missing here?
It is normal that in an auth state listener your callback first gets called with null, as that is typically the initial user authentication state as the page is loaded.
Firebase tries to automatically restore the user's authentication state, but this may take some time. Only once the state is restored will it again call your auth state listener with the then active user account.
I found a solution in case anyone runs into a similar issue - the button that was triggering the submit was inside a html form and I was not calling event.preventDefault() and so the page was re-rendering and I believe this was causing the auth callback to work incorrectly. Working code -
const createAccount = (event) => {
event.preventDefault();
app
.createUserWithEmailAndPassword(email, password)
.then((user) => {
console.log("user created");
console.dir(user);
})
.catch((error) => {
console.log("something went wrong");
});
};

why is my firebase authentication display name not getting updated properly with react js?

I am trying to build firebase authentication for my react app..After I sign in I am trying to update the displayName and then redirect..On the redirected page I am trying to greet the user by fetching the display name saved while signing up with firebase..This page works properly immediately after I redirect but if I reload this page then it is not able to show the displayName and throws this error:
TypeError: Cannot read property 'displayName' of null
This is the function which gets triggered when signup button is clicked..
const signup = async () => {
try{
await firebaseApp.auth().createUserWithEmailAndPassword(email, password)
await firebaseApp.auth().currentUser.updateProfile({displayName:username})
console.log(firebaseApp.auth().currentUser)
if (!firebaseApp.auth().currentUser){
setLoading(true)
}
history.push('/home')
}catch (error){
alert(error.message)
}
}
This is the JSX of the page which is being redirected to by signup page:
<div className="greetings">
Good Evening {firebaseApp.auth().currentUser.displayName}
</div>
Why is this issue happening and how to resolve it?
firebaseApp.auth().currentUser is always null when a page first loads. It won't contain a User object until some time later, after the SDK is able to load and verify the auth token for that user. Instead of using currentUser, you should set up an auth state observer as shown in the documentation. This observer will get invoked as soon as the User object is known.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
// ...
} else {
// User is signed out.
// ...
}
});
You can use the results of this observer function to know when the user is signed in or signed out over time. To learn more about how it works, read this blog post.

How to get get username (or any other data) of the logged in user with React/Redux

What would be the best and secure way to store a user logged in data (mainly username or uuid). So, I can use it in my other components? I have thought of several ways to do it.
Upon login/signup, store username in local storage, and remove it when authsession ends. (Very easy to implement, but most likely the least secure)
Pass email through the AuthLogin/AuthSignup methods, but I can't seem to figure out how to pass it through router. I also notice that this doesn't work if the browser gets refreshed, even if the user is still logged in.
Cookies (don't know how to use them with react yet).
My authLogin method:
export const authLogin = (email, password) => {
return dispatch =>{
dispatch(authStart());
axios.post('http://127.0.0.1:8000/rest-auth/login/',{
email: email,
password: password,
})
.then(res => {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token',token);
localStorage.setItem('expirationDate',expirationDate)
localStorage.setItem('email', email)#method 1
console.log("My token",localStorage.getItem('token'))
dispatch(authSuccess(token,email));#method 2
dispatch(checkAuthTimeout(3600));
})
.catch(err => {
dispatch(authFail(err))
})
}
}
I think you have the right idea. Authenticate the user as you are doing, store the token in localStorage, and save the user data in redux. Since refreshing causes the redux store to start fresh, and you still need your user data I would dispatch an action somewhere in your app, probably around the root of the app when it first mounts to get the logged in user data based on the token saved in localStorage.
Something like this:
class App extends Component {
componentDidMount() {
// User will come from the store. If we do not have a user,
// but we have a token, then the user is logged in
if (!this.props.user && sessionStorage.getItem('token')) {
// a redux dispatch function to authenticate a JWT
this.props.validateJWT();
}
}
}
As far as passing props through the router you would have to use a renderProp, which may be a little tricky. Can you please share your code for your router issue?

Passport.js - Send back data to react/redux app, while redirecting after login

I'm creating a React/Redux app and am new to setting up the backend with Node/Express and authentication with Passport. What I'd like to have happen is the user logs in, credentials are checked, and if user is found from database, redirect the user to the next page and return user's info so I can update reducer.
My endpoint to login works as I think it does, as I'm able to access req.user object and am getting back the correct user info upon successful authentication. Where I'm stuck is redirecting the user to another page, while also passing along that user's info so I may update redux store.
EDIT: So I updated response by sending user info after authentication as well as the next route to redirect the user. Since I'm using redux, I perform login action, and upon getting a response, I redirect user with window.location. This partially solves my problem as even though I redirect after logging in, I believe that after updating window.location, my redux store gets refreshed, and I lose any information I grabbed server side.
app.post('/api/login', passport.authenticate('local-login', { failureRedirect: '/login'}), (req, res) => {
res.json({
redirect: '/profile
userInfo: req.user.dataValues})
})
})
export const login = (email, password) => {
return(dispatch) => {
axios
.post('/api/login', {
email: email,
password: password
})
.then(resp => {
dispatch({
type: 'LOGIN_USER',
data: resp.data
})
window.location = resp.data.redirect
})
.catch(errors => {
console.log(erros)
})
}
}
You're losing your state because you are forcing a page reload with window.location = resp.data.redirect. You should use a router with your application to do the url changes (such as react-router-dom). You can programmatically change the urls by using pushState (or maybe hashRouting?) instead of redirecting the way you currently are.

Categories

Resources