Right way to keep Firebase user data and session stored - React Native - firebase

I have been looking on several ways to keep the user logged in my react native application. One of those are:
auth.onAuthStateChanged((user) => {
if (user) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
});
Which isn't working for me, whenever I press r on the application to hot refresh, it losses the user.
I'm currently storing the user email, uid on the local storage, and only loggin it out whenever he presses the logout button. Is this the right way to do so? I have encountered some problems, for example. I can't use auth().currentUser since the data is stored in async storage.
This is how i'm managing it:
export default function App() {
// Autentication
const [isAuthenticated, setIsAuthenticated] = useState(false);
// Dark mode
const [darkMode, setDarkMode] = useState(false);
// Setting location
let [locale, setLocale] = useState(Localization.locale);
i18n.fallbacks = true;
i18n.translations = { en, pt };
i18n.locale = locale;
// Settings fonts
const [fontsLoaded] = useFonts({
Roboto_400Regular,
Roboto_500Medium,
Roboto_700Bold,
});
useEffect(() => {
setLocale('pt');
getData('#rememberUser')
.then((data) => data && setIsAuthenticated(true))
.catch(() => console.log('No user on async'));
// auth.onAuthStateChanged((user) => {
// if (user) {
// setIsAuthenticated(true);
// } else {
// setIsAuthenticated(false);
// }
// console.log(user);
// });
}, []);
if (!fontsLoaded) return <></>;
return (
<DataProvider
setIsAuthenticated={setIsAuthenticated}
setDarkMode={setDarkMode}
darkMode={darkMode}
>
<ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
{!isAuthenticated ? (
<NavigationContainer>
<AuthRoutes />
</NavigationContainer>
) : (
<Provider store={store}>
<Main />
</Provider>
)}
<ToastMessage />
</ThemeProvider>
</DataProvider>
);
}
storing:
async function storeData(value: any) {
try {
await clearAll();
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem('#rememberUser', jsonValue);
} catch (error) {
console.log('Error on async storage');
}
}
Is this the right way to keep track of the user?

Related

messaging().getToken() generates same device token for different devices

I got an issue with fcm tokens, they are identical for some devices (as you can see from screenshot). On internet it is said that they should be unique for each device, but it seems that in our case they are not. This is the way how I get fcm tokens from messaging library (react native firebase):
export const AppMaintainer = () => {
const fullname = useAppSelector(getMyFullName);
const photoUrl = useAppSelector(getPhotoUrl);
const userDocId: string = useAppSelector(getCurrentUserDocId);
const token: TokenOrProvider = useAppSelector(getCurrentStreamToken);
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(actions.authStateChangeUser());
}, []);
const requestUserPermission = async () => {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
const deviceToken = await getFcmToken();
try {
await firestore()
.collection('usersDescriptiveData')
.doc(userDocId)
.update({
deviceToken,
});
} catch (error: any) {
console.log('error in deviceToken update');
dispatch(
globalActions.setIsGlobalSnackbarVisible({message: error.message}),
);
}
}
};
const getFcmToken = async () => {
try {
const fcmToken = await messaging().getToken();
return fcmToken;
} catch (error) {
console.log('error in fcm', error);
}
};
useEffect(() => {
if (userDocId && photoUrl && token && fullname) {
requestUserPermission();
}
}, [userDocId, photoUrl, token, fullname]);
return (
<>
<NavigationContainer ref={navigationContainerRef}>
<RootNavigator />
</NavigationContainer>
<NetGlobalSnackbar />
</>
);
};
Could you please say what i am doing wrong?
Package.json:
"react-native": "0.69.6",
"#react-native-firebase/messaging": "12.9.3".
Additionally, I assume that these duplicated tokens are the reason why some users get notifications more then two times (but this is another story).
I tried calling the getFsmToken function again when deviceToken was already in use by another user, but it didnt help. Additionally, tried deleting and generating the deviceToken again, but it didnt help too. I expected this token to be unique for each device, but it is not, which means i am doing something wrong. FYI: i dont do it with browser, the app is available on stores and some users get the same token for their devices
Could anyone guide me with this?

next-redux-wrapper: after hydration useSelector returns initial value (null), but getServerSideProps passes the correct value to the page

I got getServerSideProps like this, which gets token from cookie and gets uses it to fetch user data with RTKQ endpoint. Then dispatches that data to authSlice.
So far it's good.
const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req, res }: GetServerSidePropsContext) => {
let result: AuthState = null;
const data = getCookie('device_access_token', { req, res });
if (data?.toString()) {
result = await store
.dispatch(
usersApi.endpoints.getUserByToken.initiate(data?.toString())
)
.unwrap();
}
if (result) store.dispatch(setUser(result));
return { props: { auth: result } };
}
);
Then I merge this auth data in the store like this:
const reducer = (state: ReturnType<typeof rootReducer>, action: AnyAction) => {
if (action.type === HYDRATE) {
console.log('payload#HYDRATE', action.payload);
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.auth.user) {
nextState.auth.user = state.auth.user;
nextState.auth.token = state.auth.token;
} // preserve auth value on client side navigation
return nextState;
} else {
return rootReducer(state, action);
}
};
console.log('payload#HYDRATE', action.payload); also shows correct data.
The problem is in a page where I export getServerSideProps,
const IndexPage: NextPage = ({ auth }: any) => {
console.log('user#index', auth);
console.log('userSelect#index', useSelector(selectCurrentUser));
return auth ? <Home /> : <NoAuthHome />;
};
auth shows correct value, but useSelector(selectCurrentUser) shows null
Can someone tell me if this is how it is intended to be, or I'm doing something wrong?
Because I don't want prop-drilling auth on countless pages, just use useSelector(selectCurrentUser) wherever necessary.
Finally found the problem!
problem was in _app.tsx
I wrapped <Component {...pageProps} /> with <Provider store={store} at the same time exporting with wrapper.withRedux(MyApp)

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.

React Native Firebase: how to check authorization?

After calling "createUserWithEmailAndPassword" or "signInWithEmailAndPassword", a property "currentUser" becomes filled in "auth()". It remains full after restarting an application, even if I remove this user from the Firebase console.
How can I check user's authorization when the application starts?
To force the client to "check in" with the server, you would use the User#getIdToken() method by calling firebase.auth().currentUser.getIdToken(true). If the user has been deleted, this should reject with the error code 'auth/user-token-expired'.
From the React Native Firebase Quick Start documentation, I'll use this as the base of the MWE:
import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
import auth from '#react-native-firebase/auth';
function App() {
// Set an initializing state whilst Firebase connects
const [initializing, setInitializing] = useState(true);
const [user, setUser] = useState();
// Handle user state changes
function onAuthStateChanged(user) {
setUser(user);
if (initializing) setInitializing(false);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
if (initializing) return null;
if (!user) {
return (
<View>
<Text>Login</Text>
</View>
);
}
return (
<View>
<Text>Welcome {user.email}</Text>
</View>
);
}
Once the user has logged in or their cached access has been restored, onAuthStateChanged will receive an event with the current user object. Here we add the ID token request.
function onAuthStateChanged(user) {
if (!user) {
// not logged in
setUser(user);
if (initializing) setInitializing(false);
return;
}
user.getIdToken(/* forceRefresh */ true)
.then(token => {
// if here, this user is still authorised.
setUser(user);
if (initializing) setInitializing(false);
}, error => {
if (error.code === 'auth/user-token-expired') {
// token invalidated. No action required as onAuthStateChanged will be fired again with null
} else {
console.error('Unexpected error: ' + error.code);
}
});
}

firebase auth return null on signInWithPhoneNumber

I am trying to login with phone number with firebase signInWithPhoneNumber() method for login. In which i have checked whether user auth state has been change or not. If user auth is change then login and redirect to home page. but i m getting auth null
onLoginBtnClicked() {
const { contact, password } = this.props;
const error = Validator('password', password) || Validator('contact', contact);
if (error !== null) {
Alert.alert(error);
} else {
console.log('else');
// this.props.loginUser({ contact, password});
const mobileNo = '+91'+contact;
firebase.auth().signInWithPhoneNumber(mobileNo)
.then(data => console.log(data),
firebase.auth().onAuthStateChanged((user) => {
console.log('user'+user);
if (user && !CurrentUser.isFirstTimeUser) {
const userRef = firebase.database().ref(`/users/`);
userRef.on("value", (snapshot) => {
console.log(snapshot.val());
snapshot.forEach(function(item) {
var itemVal = item.val();
if(itemVal.mobile == contact){
NavigationService.navigate('Home');
}
});
}, (errorObject) => {
console.log("The read failed: " + errorObject.code);
});
//NavigationService.navigate('Home');
}
})
)
.catch(error => console(error.message) );
}
}
There are two things to note here
onAuthStateChanged is a listener which listen for the user auth changes.
signInWithPhoneNumber sends the code to the user's phone, you have to confirm it to authenticate the user.
You need to add the listener in the react lifecycle for the component once it is mounted and remove it when it is unmounted
componentDidMount() {
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ user: user.toJSON() });
} else {
// Reset the state since the user has been logged out
}
});
}
componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe();
}
// Send Message here
firebase.auth().signInWithPhoneNumber(mobileNo)
.then(confirmResult => this.setState({ confirmResult })
.catch(error => // handle the error here)
// Authenticate User typed Code here
const { userCode, confirmResult } = this.state;
if (confirmResult && userCode.length > 0) {
confirmResult.confirm(codeInput)
.then((user) => {
// handle user confirmation here or in the listener
})
.catch(error => // handle the error here)
}

Resources