Issues with firebase google-Oauth logout in react - reactjs

I am facing an issue in my React app when implementing Google Auth using Firebase. After a successful login, I need to check if the user exists in my Firebase database. If the user does not exist, I log them out. The issue I am facing is that after logging out, I am unable to sign in again as the authentication popup does not appear. I believe this can be due to caching of the current user in the browser.
Here's my current signInWithGoogle function:
const signInWithGoogle = async () => {
const result = await signInWithPopup(auth, provider);
console.log(result.user)
const mailId = result.user.email ? result.user.email:""
const Isvalid = await validate(mailId);
if (Isvalid) {
console.log('validated user')
navigate('/dashboard')
} else {
signOut(auth).then(() => {
console.log('Signout Successful');
}).catch((err) => {
console.log('Error', err);
})
alert('not a valid user')
}
}

Related

Update user details on Firestore Database - web V9

I've been trying to figure out this problem for the last few weeks - I've read the full documentation for Firebase and searched from the previously answered questions on here with no luck.
I have a web application made in React.
The 'getUserDetails' and 'updateUserDetails' functions are on a page called SignUp.js.
The 'button' where the user clicks is on a page called 'Profile.js'.
The 'getUserDetails' function works fine and returns the data correctly. However, when I try to update a user's age, height.. etc., I get one of a few errors when I play around with it:
"Error updating user details"
"Blocked by DevTools" XHR error
"Error updating user details: FirebaseError: Expected type 'ya', but it was: a custom Ia object'
My goal here is just to enable a user to be able to update their details in the firebase DB from their 'profile' page.
// SignUp.js
const getUserDetails = async (uid) => {
const user = auth.currentUser;
if (user) {
const userRef = doc(collection(db, "users"), uid);
const detailsRef = collection(userRef, "details");
return getDocs(detailsRef);
} else {
console.log("No user logged in");
}
}
const updateUserDetails = async (uid, updatedDetails) => {
try {
const userRef = doc(collection(db, "users"), uid);
const detailsRef = collection(userRef, "details");
const docSnap = await getDoc(detailsRef);
console.log(detailsRef);
if (docSnap.exists()) {
await updateDoc(detailsRef, updatedDetails);
console.log("User details updated in Firestore!");
return true;
} else {
console.log("No such document!");
return false;
}
} catch (e) {
console.error("Error updating user details: ", e);
return false;
}
}
The below is the handleUpdate that is call when the button is clicked.
// Profile.js
const handleUpdate = () => {
const auth = getAuth();
const uid = auth.currentUser.uid;
updateUserDetails(uid, userDetails).then(r => {
if (r) {
console.log("User details updated successfully!");
} else {
console.log("Error updating user details");
}
});
}
Can anyone see where I might be going wrong?

onAuthStateChanged and getRedirectResult returning null user after successful google login redirect

Somewhat randomly my login flow stops working for some users when using google as a login provider (possibly facebook too, unsure) on iOS. They are able to log in on desktop using google login with the same app bundle. I think this is happening for ~1/10 users.
After a user selects which google account to use, the google redirects back to my app. The onAuthStateChange triggers, but the user is null. This is after successfully "logging in" with google. I think this may have to do with users trying to use multiple auth providers and getting stuck in a weird state. I'm not receiving any error or console logs when this happens, it's as if it's a fresh page load instead of a redirect.
I haven't been able to reproduce the issue using my own account until recently. I tried reverting back to earlier builds when the issue wasn't present for my account, with no luck. I'm now unable to log into my own app 😂
in UserProvider.tsx:
firebaseConfig.apiKey = process.env.REACT_APP_AUTH_API_KEY;
firebaseConfig.authDomain = process.env.REACT_APP_AUTH_DOMAIN;
firebaseConfig.projectId = "redacted";
firebaseConfig.storageBucket = "redacted.appspot.com";
firebaseConfig.messagingSenderId = "redacted";
firebaseConfig.appId = "redacted";
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Firebase Authentication and get a reference to the service
const auth = initializeAuth(app, {
popupRedirectResolver: browserPopupRedirectResolver,
persistence: [indexedDBLocalPersistence, browserLocalPersistence, inMemoryPersistence],
errorMap: debugErrorMap
});
const UserContextProvider = (props) => {
const signInWithGoogle = () => {
signInWithRedirect(auth, googleAuthProvider);
}
const handleUserCredential = async (result: UserCredential) => {
if (result.user) {
const additionalInfo = getAdditionalUserInfo(result)
dispatch({
type: 'SET_ADDITIONAL_USER_INFO', data: {
additionalInfo: additionalInfo,
}
});
}
}
useEffect(() => {
dispatch({ type: 'LOGIN_INITIATED' });
const handleRedirectResult = async () => {
alert("handling redirect result");
const result = await getRedirectResult(auth);
alert(`redirect result: ${result}`);
if (result) {
handleUserCredential(result)
}
}
onAuthStateChanged(auth, async (user: User | null) => {
alert(`on auth change fired: ${user}`);
if (user) {
// set the token now
const token = await getIdToken(user);
const refreshToken = async () => {
return await getIdToken(user, true);
}
onLoginSuccess(user, token, refreshToken);
} else {
onLogoutSuccess();
}
}, (error) => {
onError(error)
});
handleRedirectResult();
}, [])
onLogoutSuccess() is triggering because user is null.
PS: I spammed a bunch of alerts in there because I'm struggling to debug on my device, those aren't in my production build.

How to Connect Firebase Auth with Google One Tap Login

I have created a web app with Firebase and React.js and implemented sign-in with Google. I then tried to implement GoogleOneTapSignin and the one-tap-sign-in UI is working successfully because I used the react-google-one-tap-login npm package.
If may react app I have a function that listens for AuthStateChange and then either registers the user if they are new or sign in them if they are already a member and also updates the state if they logged. out.
Now that I have implemented google-one-tap-login, I was expecting the onAuthSTaetChanged function to be triggered if a user signs in using the google-one-tap-login but it is not the case.
Below is the part of my App.js code that handles the user auth.
const classes = useStyles();
const dispatch = useDispatch();
const alert = useSelector(state => state.notification.alert);
// Handling Google-one-tap-signin
useGoogleOneTapLogin({
onError: error => console.log(error),
onSuccess: response => {
console.log(response);
const credential = provider.credential(response);
auth.signInWithCredential(credential).then(result => {
const {
user
} = result;
console.log(user);
});
},
googleAccountConfigs: {
client_id: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
});
//Handling firebase authentification
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
// If there is a user create the user profile and update useState
if (user) {
// createUserProfile function creates the user profile in firestore if they are new
const userRef = await createUserProfileDocument(user);
userRef.onSnapshot(snapshot => {
const doc = snapshot.data();
dispatch(
setUser({
id: snapshot.id,
...doc
})
);
});
} else {
dispatch(setUser(null));
}
});
return () => {
unsubscribe();
};
}, [dispatch]);
I tried to implement the solution suggested by the 2nd answer in this StackOverflow question but I get the error below on the console. when I use google-one-tap-sign-in. Remember I am not using the FirebaseUi library. So far my application only uses the sign in with Google
t {
code: "auth/argument-error",
message: "credential failed: must provide the ID token and/or the access token.",
a: null
}
a: null
code: "auth/argument-error"
message: "credential failed: must provide the ID token and/or the access token."
The ID token required by Firebase's signInWithCredential function exists within the credential property of the response object. Here is a sample function below using Firebase V8.
// firebase V8
function handleCredentialResponse(response) {
if (response) {
const cred = auth.GoogleAuthProvider.credential(response.credential);
// Sign in with credential from the Google user.
return auth().signInWithCredential(cred);
}
}
Firebase v9
// firebase V9
import { getAuth, GoogleAuthProvider, signInWithCredential } from "firebase/auth";
const auth = getAuth();
function handleCredentialResponse(response) {
if (response) {
const cred = GoogleAuthProvider.credential(response.credential)
// Sign in with credential from the Google user.
return signInWithCredential(auth, cred);
}
}
The response param is a credential response returned from the Google one-tap function callback.
google?.accounts.id.initialize({
client_id: your-google-app-client-id.apps.googleusercontent.com,
callback: handleCredentialResponse,
});
google?.accounts.id.prompt((notification) => {
console.log(notification);
});

Persist auth state in react/react native for Firebase

I am using react native for an ios app and firebase for authentication. Every time I leave the app and come back, it asks for a login. I want to persist the firebase login but don't really know where to put it.
I know I need to put this in:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
I have the following signIn function that runs when the login button is pressed on the signInScreen:
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
I have the following signIn stuff in my firebaseContext:
const Firebase = {
getCurrentUser: () => {
return firebase.auth().currentUser;
},
signIn: async (email, password) => {
return firebase.auth().signInWithEmailAndPassword(email, password);
},
getUserInfo: async (uid) => {
try {
const user = await db.collection("users").doc(uid).get();
if (user.exists) {
return user.data();
}
} catch (error) {
console.log("Error #getUserInfo", error);
}
},
logOut: async () => {
return firebase
.auth()
.signOut()
.then(() => {
return true;
})
.catch((error) => {
console.log("Error #logout", error);
});
},
};
Where do I put the persist code I listed above from the docs?
Thanks!
When do you check if someon is signed in or not?
From the code shown it looks like you check it manuelly by calling currentUser. You have to consider that the persistance of auth state is asynchronous. That means if you call currentUser on auth before the localy saved auth state is loaded you would get there null and thing that the user is not signed in.
To get the auth state Firebase recommend to use the onAuthStateChanges event listener. With that you can listen to auth state changes no matter if you logged in or the persistet auth state is loaded.
The usage is very simple:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
That is the reson I asked where you check if someon is signed in or not. If I could see that code I could help you adopt it to use that event listener.

How to log in user with Google (through Firebase) in a React app with functional components?

I am trying to log in a user with Google in my React/Firebase app. I've followed a tutorial on youtube (https://www.youtube.com/watch?v=umr9eNbx3ag) but the results are different. When I click the Log In button, I get redirected to Google, choose an account and then get redirected to my site.
It seems as my 'if' statement never runs, auth.currentUser never evaluates to true.
This is my Firebase file
firebase.initializeApp(firebaseConfig)
export const firestore = firebase.firestore()
export const auth = firebase.auth()
export const provider = new firebase.auth.GoogleAuthProvider()
export const signInWithGoogle = () => auth.signInWithRedirect(provider)
export const signOut = () => auth.signOut()
export default firebase
This is my log in component
import { auth, signInWithGoogle, signOut } from '../../Firebase/Firebase'
const LoginOrRegister = () => {
const { username, setUsername, idToken, setIdToken } = useContext(Context)
useEffect(() => {
auth.onAuthStateChanged(async nextUser => {
if (auth.currentUser) {
setIdToken(await auth.currentUser.getIdToken())
setUsername(auth.currentUser.displayName)
} else {
setIdToken(null)
}
})
}, [])
return (
<div>
<LogInForm>
<button onClick={signInWithGoogle}> Log in with Google </button>
</div>
)
Since you are using signInWithRedirect you need to make use of auth.getRedirectResult() instead of auth.onAuthStateChanged as you are actually navigating away from the app and coming back in
Below code will work or you.
useEffect(() => {
auth
.getRedirectResult()
.then(function(result) {
console.log(result);
if (result.credential) {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
setToken(token);
// ...
}
// The signed-in user info.
var user = result.user;
console.log(user);
setData(user);
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
console.log(errorCode, errorMessage);
// ...
});
}, []);
You can find the reference documentation here

Resources