How to properly persist login state using FireBase in React Native EXPO? - firebase

I used email and password to sign in through Firebase. However, once I reload the app, I need to sign in again. Is there a way to automatically log user in once the app is reloaded?
I am using EXPO managed project with functional structure btw, not with class structure.

if you're still facing this problem.
Here's how I've done it.
Here's my App.js
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
return firebase.auth().onAuthStateChanged(setLoggedIn);
}, []);
if (loggedIn) {
return <HomeScreen />
} else {
<Login />
}
Here's the login code.
I have a simple form with email and password and a button, when pressed, this functions is called.
const handleLogin = async () => {
await firebase
.auth()
.signInWithEmailAndPassword(email, password)
.catch((err) => {
setVisible(true);
setModalMessage(err.message);
});
};
Let me know if this worked.

Related

Reloading Expo app logs out Firebase User

I have an Expo app that I am building that uses Firebase. I have the following code that handles when a user's auth state changes. When it changes, it sets a context property and shows a specific stack dependent on the status of the user.
export default function RootNavigator() {
const { user, setUser } = useContext(AuthenticatedUserContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const unsubscribeAuthStateChanged = getAuth().onAuthStateChanged((authenticatedUser) => {
if (authenticatedUser) setUser(authenticatedUser);
else setUser(null);
setIsLoading(false);
console.log(authenticatedUser);
});
return unsubscribeAuthStateChanged;
}, [user]);
if (isLoading) {
return <LoadingScreen />;
}
return <NavigationContainer>{user ? <AppStack /> : <AuthStack />}</NavigationContainer>;
}
However, when I reload the expo app by pressing "R" in the terminal, my firebase user is null again.
I am using the following code to log a user in:
signInWithEmailAndPassword(auth, emailAddress, password).catch((error) => handleSignInError(error));
Any help would be greatly appreciated. Thank you!

How can I log in a user right after his/her email has been verified using firebase/auth and react-native without creating a whole landing page?

Notice: I have seen this question, but creating a whole landing page just to verify a user seems a bit much.
I added a login functionality to my react-native app using firebase/auth with email and password. This works well so far and I have no issues doing that.
I then continued to send a verification email to a new user and only allow him/her to use the app, once the email is verified. Again, no issues here.
The next step would be to login the user right after the email was verified. This is where I'm stuck, since the onAuthStateChanged eventhandler doesn't update after the user pressed the verification link in the email.
Is there any way to listen to the emailVerified state in real-time? I tried to use polling with setInterval() but this is not great since there is a notable delay between verification and login. I read about a continueLink you can pass to sendEmailVerification, but I couldn't figure out how to make that work in react-native.
I'm using Expo and therefore the Firebase SDK, not the Firebase react native package.
Here is the code I use for the signup:
export const signUp = async (username: string, email: string, password: string) => {
try {
const auth = getAuth();
if (email && password && username) {
// sign up
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
// save username in firestore
await setUserName(userCredential, username);
// send Email Verification
await sendEmailVerification(userCredential.user);
return true;
}
} catch (error) {
onError(error);
}
};
And this is my onAuthStateChanged handler:
auth.onAuthStateChanged(authenticatedUser => {
try {
if (authenticatedUser?.emailVerified) {
setUser(authenticatedUser)
} else {
setUser(null)
}
} catch (error) {
console.log(error);
}
});
So in the end I did follow this question, but I changed it a bit to fit my needs. I'll post my steps for anyone who's doing the same.
Create a simple static website with firebase init and host it on firebase or somewhere else (check the hosting tab in your firebase console to get started)
Follow this guide to create the appropriate handlers on the website
Add the following to your verificationHandler to update the user (don't forget to import firestore) (I send the userId via the continueURL, but there are probably better ways)
// You can also use realtime database if you want
firebase.firestore().collection("users").doc(userId).set({
emailVerified: true
}, {merge: true}).then(() => {
message.textContent = "Your email has been verified.";
}).catch((error) => {
message.textContent = "The verification was invalid or is expired. Please try to send another verification email from within the app.";
});
Got to authentication -> templates in your firebase console and change the action url to your hosted website's url
Add a listener to the firestore doc to your react-native app
const onUserDataChanged = (uid, callback) => {
onSnapshot(doc(firestore, "users", uid), doc => callback(doc.data()));
}
Use the data from the callback to update the login state in the app
// As an example
auth.onAuthStateChanged(authenticatedUser => {
if (authenticatedUser && !authenticatedUser.emailVerified) {
unsubscribeFirestoreListener?.();
unsubscribeFirestoreListener = onUserDataChanged(authenticatedUser.uid, (data: any) => {
if (data?.emailVerified) {
setUser(authenticatedUser);
unsubscribeFirestoreListener?.();
}
});
}
}
use the codes below for your authentication context. for user id, you should use 'user.uid'
import React, { useState, createContext } from "react";
import * as firebase from "firebase";
import { loginRequest } from "./authentication.service";
export const AuthenticationContext = createContext();
export const AuthenticationContextProvider = ({ children }) => {
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
firebase.auth().onAuthStateChanged((usr) => {
if (usr) {
setUser(usr);
setIsLoading(false);
} else {
setIsLoading(false);
}
});
const onLogin = (email, password) => {
setIsLoading(true);
firebase.auth().signInWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onRegister = (email, password, repeatedPassword) => {
setIsLoading(true);
if (password !== repeatedPassword) {
setError("Error: Passwords do not match");
return;
}
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onLogout = () => {
setUser(null);
firebase.auth().signOut();
};
return (
<AuthenticationContext.Provider
value={{
isAuthenticated: !!user,
user,
isLoading,
error,
onLogin,
onRegister,
onLogout,
}}
>
{children}
</AuthenticationContext.Provider>
);
};

React Native: firebase auth/network-request-failed error when trying to create a new user, but not on login?

So I have a simple React Native application where I added firebase into. I've added a firestore and setup Authorization on my firebase account. When trying to login with the set account, the login succeeds using the following code:
const onButtonPress = () => {
setErrorMessage(null);
const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
.then((res) => {
navigation.navigate('Main'); // When going to the same Stack.Screen you're already on, it won't do anything
})
.catch((error) => {
setErrorMessage('Error: ' + error.message);
});
}
However, when I try to register in the same manner, it fails with the following error:
Firebase: Error (auth/network-request-failed)
This is my register code:
const onButtonPress = () => {
if (!email || !password || !passwordRepeat) {
setErrorMessage('Please fill in the entire form');
} else if (password === passwordRepeat) {
const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)
.then((res) => {
setErrorMessage(null);
navigation.navigate('Main');
})
.catch((error) => {
setErrorMessage('Error: ' + error.message);
});
} else {
setErrorMessage('Error: passwords don\'t match');
}
}
The "funny" thing is, when I try to login with a wrong account/password combination with my login-code, it gives the same error.
So since login works, but not with wrong account/pw combination and getting the same error as Register, I'm guessing my database setup etc is correct (since I can login), but during the create-account, something goes wrong?
Does anyone know what could be causing this and how I can fix it so that I can create new users in my app?
EDIT: I'm also using Expo in this application and using "expo start" to test it on my personal Android device.
I was facing the same issue up until now, turns out I using onChange in the instead of onChangeText, and using it to set the state for the email and password, which in turn was setting the email and password as an object instead of a string.
Example:
<TextInput
placeholder="Password"
value={password}
onChange={newText => {setPassword(newText)} //This should be onChangeText
placeholderTextColor="black"
style={styles.textInput}
secureTextEntry
/>
Try to console.log() the values for your email and password before creating the user, perhaps you're doing something similar.

React Native Google Signin idToken null

I am using react-native 0.60.5, with firebase authentication (using package react-native-firebase and react-native-google-signin).
Everything looks good to me and the google sign return an object with the user logged in, but the idToken is always null. I need to get the idToken to perform the authentication in firebase.
import { GoogleSignin } from 'react-native-google-signin';
export const googleLogin = async () => {
GoogleSignin.configure();
const userInfo = await GoogleSignin.signIn();
//here we have the issue. userInfo cotains all google user informations except the idToken
//userInfo.idToken is null
}
how can I fix the google-sign-in to return the idToken?
Add the web client id which is availble in Firebase authentication/signInmethod/Google:
function configureGoogleSign() {
GoogleSignin.configure({
webClientId: WEB_CLIENT_ID,
offlineAccess: false
})
}
This is working for me.
You have not specified webclientId.
and make sure to configure google signIn in useEffect Method so when you load the screen it already configured.
useEffect(() => {
GoogleSignin.configure({
webClientId:
'YOUR_WEBCLIENT_ID',
});
}, []);

Get current users access token from Firebase in React Native

I am trying to get the Firebase authentication access token within a React Native application so that I can authenticate my API calls to a custom server. The Firebase documentation says I should get this token by using auth().currentUser.getIdToken(); however currentUser returns null.
I've tried to use getIdToken() in multiple areas of the application. I know the access token is generated as I can see it in the logs while using expo (user.stsTokenManager.accessToken).
Why is currentUser returning null and how can I get the accessToken?
You need to wrap user.getIdToken() inside of firebase.auth().onAuthStateChanged for user to be available. You can then use jwtToken in your header to authenticate your API calls. You need to import your Firebase configuration file for this to work.
let jwtToken = firebase.auth().onAuthStateChanged(function(user) {
if (user) {
user.getIdToken().then(function(idToken) { // <------ Check this line
alert(idToken); // It shows the Firebase token now
return idToken;
});
}
});
Just putting await before will work too just like this:
await auth().currentUser.getIdToken();
getIdToken returns a promise
firebase.auth()
.signInWithCredential(credential)
.then(async data => {
const jwtToken = await data.user?.getIdToken();
console.log(jwtToken);
})
Hook example
Unfortunately, its not reliable to directly get the token. You first have to listen to the authentication state change event which fires upon initialization since its asynchronous.
import {auth} from '../utils/firebase'
import {useState, useEffect} from 'react'
export default function useToken() {
const [token, setToken] = useState('')
useEffect(() => {
return auth().onAuthStateChanged(user => {
if (user) {
user.getIdToken(true)
.then(latestToken => setToken(latestToken))
.catch(err => console.log(err))
}
})
}, [])
return token
}
then use like so in your functional component
const token = useToken()
useEffect(() => {
if (token) {
// go wild
}
}, [token])

Resources