Persist authentication with React Native / Redux and Firebase - firebase

I have a fairly simple React native / Redux app and have recently added Redux persist with AsyncStorage to help carry over the state upon reload.
However, I am having issues getting Firebase to re-authenticate the user. I was wondering if anyone has experience as I am fairly new to Redux as well as Firebase. To be more clear, I would like the user to login once, and then not have login again every time they open the app.
my login user action:
export const loginUser = ({ email, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
firebase.auth().signInWithEmailAndPassword(email, password)
.then(user => loginUserSuccess(dispatch, user))
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(user => loginUserSuccess(dispatch, user))
.catch(() => loginUserFail(dispatch));
});
};
};
My App.js:
class App extends Component {
constructor() {
super();
this.state = {
rehydrated: false,
store
};
}
componentDidMount() {
const persistor = persistStore(
store,
{
storage: AsyncStorage,
whitelist: ['auth']
},
() => { this.setState({ rehydrated: true }); }
);
AppState.addEventListener('change', () => this.handleAppLoaded(AppState.currentState));
const firebase_config = {
apiKey: Config.FIREBASE_API_KEY,
authDomain: `${Config.FIREBASE_PROJECT_NAME}.firebaseapp.com`,
databaseURL: `https://${Config.FIREBASE_PROJECT_NAME}.firebaseio.com`,
storageBucket: `${Config.FIREBASE_PROJECT_NAME}.appspot.com`,
messagingSenderId: Config.FIREBASE_MESSAGE_ID
};
firebase.initializeApp(firebase_config);
}
componentWillUnmount() {
AppState.removeEventListener('change', () => this.handleAppLoaded(AppState.currentState));
}
handleAppLoaded(state) {
if (state === 'active') {
store.dispatch(renewToken(store.getState()));
}
return null;
}
render() {
if (!this.state.rehydrated)
return null;
this.handleAppLoaded(AppState.currentState);
return (
<Provider store={store}>
<RouterWithRedux />
</Provider>
);
}
}
export default App;
Could someone point me in the right direction? I have tried making a reauthUser action, but got stuck as I could not find a way to reauthenticate the user, as the Auth object doesn't hold the password (obviously for security reasons)

Are you using the web SDK? If you are, then first you need to using this instead https://github.com/invertase/react-native-firebase
These libraries are for the react-native project with native support.
If you are already using it, you could check the API here https://rnfirebase.io/docs/v3.2.x/auth/reference/auth
Setup all the onUserChanged, onAuthStateChanged etc. Then your app should no problem with re-authenticate stuff.

Related

User must log in upon each app refresh after upgrading the Firebase JavaScript SDK and Expo SDK 44 | Expo Firebase

I recently upgraded to Expo SDK 44 as well as Firebase JavaScript SDK 9.6.10.
I am using Firebase compat in my Firebase configuration.
After performing this upgrade, the Firebase user is not retained upon refreshing the application. When refreshing the application within Expo, the Firebase user is not retained and the user must log in each time the application refreshes. This issue also exists in native builds.
My configuration is as follows:
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";
import "firebase/compat/functions";
import {
API_KEY,
AUTH_DOMAIN,
PROJECT_ID,
STORAGE_BUCKET,
MESSAGING_SENDER_ID,
APP_ID,
MEASUREMENT_ID
} from "#env";
const firebaseConfig = {
apiKey: API_KEY,
authDomain: AUTH_DOMAIN,
projectId: PROJECT_ID,
storageBucket: STORAGE_BUCKET,
messagingSenderId: MESSAGING_SENDER_ID,
appId: APP_ID,
measurementId: MEASUREMENT_ID
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
export { firebase };
const onLoginPress = () => {
if (!email) {
alert("Enter a valid email address");
return;
};
if (!password) {
alert("Enter Password");
return;
};
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((response) => {
const uid = response.user.uid
const usersRef = firebase.firestore().collection('users')
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
alert("User does not exist anymore.")
return;
}
const user = firestoreDocument.data();
user.id = firestoreDocument.id
return navigation;
})
.catch(error => {
alert(error)
});
})
.catch(error => {
alert(error)
})
}
useEffect(() => {
firebase.auth().onAuthStateChanged(User => {
if (User) {
usersRef
.doc(User.uid)
.get()
.then((document) => {
const userData = document.data()
setUser(userData)
userGlobal = User.uid;
if (!(Platform.OS === 'web')) {
registerForPushNotificationsAsync(User.uid).then(token => setExpoPushToken(token));
registerBackgroundFetchAsync().then((result) => {
console.log(result)
}).catch((error) => {
console.error("Error registering background task service: ", error)
});
registerBackgroundNotificationsAsync().then((result) => {
console.log(result)
}).catch((error) => {
console.error("Error registering background notifications service: ", error)
})
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
Analytics.logEvent('notificationReceived');
Notifications.getBadgeCountAsync().then(result => {
Notifications.setBadgeCountAsync(result + 1)
})
});
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
Notifications.getBadgeCountAsync().then(result => {
Notifications.setBadgeCountAsync(result - 1)
});
});
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
}
})
.catch((error) => {
console.error(error);
setLoading(false)
})
} else {
setAuthenticated(false);
setUser(null);
userGlobal = null;
setAccount(null);
setLoading(false);
}
});
}, []);
Async storage was extracted from react-native core in react native version 0.64.3
Refactoring firebase 9.6.10 to use modular imports (v9) vs using compat (v8) namespace resolved this issue.

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>
);
};

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

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.

Why are my redux actions not firing correctly?

I am trying to implement a check for authentication and to login/logout users using redux and firebase. I have the following code:
Action Types:
export const LOGIN_REQ = 'AUTH_REQ';
export const LOGOUT_REQ = 'LOGOUT_REQ';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAILED = 'AUTH_FAILED';
export const GET_AUTH = 'GET_AUTH';
Reducers:
import * as ActionTypes from './ActionTypes';
export const auth = (state = {
isAuth: false,
user: null
}, action) => {
switch (action.type) {
case ActionTypes.LOGIN_REQ:
return { ...state, isAuth: false, user: null };
case ActionTypes.LOGOUT_REQ:
return { ...state, isAuth: false, user: null };
case ActionTypes.AUTH_FAILED:
return { ...state, isAuth: false, user: null };
case ActionTypes.AUTH_SUCCESS:
return { ...state, isAuth: true, user: action.payload };
case ActionTypes.GET_AUTH:
return state;
default:
return state;
}
}
Thunks:
export const getAuth = () => (dispatch) => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log('Get AUTH called');
dispatch(authSuccess());
}
else {
console.log('Get AUTH called');
dispatch(authFailed());
}
});
}
export const loginReq = (email, password, remember) => (dispatch) => {
firebase.auth().signInWithEmailAndPassword(email, password)
.then((cred) => {
if (remember === false) {
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
console.log('Logged In with Redux without persist');
}
else {
console.log('Logging in with Persist');
}
console.log('Dispatching Success !');
dispatch(authSuccess(cred.user.uid));
})
.catch((err) => {
console.log(err);
dispatch(authFailed(err));
});
}
export const logoutReq = () => (dispatch) => {
firebase.auth().signOut()
.then(() => dispatch(getAuth()))
.catch((err) => console.log(err));
}
export const authSuccess = (uid = null) => ({
type: ActionTypes.AUTH_SUCCESS,
payload: uid
});
export const authFailed = (resp) => ({
type: ActionTypes.AUTH_FAILED,
payload: resp
});
And I am calling it from a component as shown below:
const mapStateToProps = state => {
return {
isAuth: state.isAuth,
user: state.user
}
}
const mapDispatchToProps = dispatch => ({
getAuth: () => { dispatch(getAuth()) },
loginReq: (email, password, remember) => { dispatch(loginReq(email, password, remember)) },
logoutReq: () => { dispatch(logoutReq()) }
})
handleLogin() {
this.props.loginReq(this.state.email, this.state.password, this.state.remember);
}
handleLogOut() {
this.props.logoutReq();
}
<BUTTON onClick=()=>this.handleLogOut()/handleLogin()>
I am close to tears because I cannot figure out why my loginReq fires one or many gitAuth() methods even when i click on the button once. This happens only for the loginReq() action. I have not specified anywhere that loginReq() should fire it.
Also i have called the getAuth() method in the component did mount method of my main screen which checks authentication status once at the start of the app.
EDIT: I have console logged in the component did mount method in the main component so I know that this getAuth() call is not coming from there.
Imo the answer is badly done, try to reestructure it better, what you call "Thunks" are actually "Actions". But if I were to tell you something that could help is that maybe the problem lies in the thunk middleware config or with the way firebase is beign treated by the dispatcher, so I would say that you better try coding an apporach with the react-redux-firebase library (this one: http://react-redux-firebase.com/docs/getting_started ) it makes easier to connect redux with a firebase back end. Other great reference, the one that I learned with, is The Net Ninja's tutorial playlist about react, redux and firebase.
A friend of mine told me this has to do with something known as an 'Observer' which is in the onAuthStateChange() provided by firebase. Basically there is a conflict between me manually considering the user as authenticated and the observer doing so.

How to Add Firebase Firestore Data into SSR Nuxt Apps Vuex Store

I'm trying to set locations into a Vuex store in my Nuxt app. I've looked into using vuexfire, however, I'm unsure if this would be optimal in a SSR app or generally what is the most simple best practice.
How do you request from firebase firestore and set the state (of the 'locations' in this example)?
Would it be best to use nuxtServerInit in a SSR app?
store/index.js
import Vuex from 'vuex'
import firebase, {auth, db} from '#/services/firebaseinit.js'
const createStore = () => {
return new Vuex.Store({
state: {
user: null,
locations: [],
},
getters: {
// User
activeUser: (state) => {
return state.user
},
// Locations
loadedLocations(state) {
return state.loadedLocations
}
},
mutations: {
// User
setUser (state, payload) {
state.user = payload
},
// Locations
setLocations (state, locations) {
state.locations = locations
}
},
actions: {
// Locations
setLocations(vuexContext, locations) {
vuexContext.commit('setLocations', locations)
},
// Users
autoSignIn ({commit}, payload) {
commit('setUser', payload)
},
signInWithFacebook ({commit}) {
return new Promise((resolve, reject) => {
auth.signInWithPopup(new firebase.auth.FacebookAuthProvider())
resolve()
})
},
signOut ({commit}) {
auth.signOut().then(() => {
commit('setUser', null)
}).catch(error => console.log(error))
},
}
})
}
I haven't used vuexfire but have used firebase with nuxt and it works pretty well. this is what I did.
npm install --save firebase
create a file called firebase.js and put this sort of code in it:
import * as firebase from 'firebase'
if (!firebase.apps.length) {
firebase.initializeApp({
apiKey: '<your-api-key>',
authDomain: '<your-domain>',
databaseURL: '<your-url>',
projectId: '<your-id>',
storageBucket: '<your-bucket>'
})
}
export { firebase }
then you register that file as a plugin in nuxt.config.js
plugins: [
'#plugins/firebase.js'
],
You need to import firebase at the top of your index.js (or other file you're using it in) in the store.
import * as firebase from 'firebase'
then you can use firebase in your nuxtServerInit as you want. Eg.
actions: {
nuxtServerInit({dispatch}, context) {
return Promise.all([
dispatch('get_posts', context),
dispatch('any_other_actions', context)
]);
},
get_posts (vuexContext, context) {
return firebase.database().ref(YOUR DB).once('value')
.then(res => {
//...What you want it to do here
})
},
Firebase is pretty powerful and you'll want to read the docs for specifics about the functions you want to perform but yeah, goes good in nuxt.

Resources