Firebase Persisting Auth with Stack Navigator - firebase

Attempting to let Firebase persist authentication within the app.js of React Native by doing the following:
There is a sign in page that envokes auth() sign in via Firebase, which works fine, and redirects to the home page via navigation.replace("Home"); however, once the app is closed and relaunched on the emulator, it redirects back to sign in.
This is seemingly what the App.js looks like, I assume that the AuthStateChanged would be prevalent as depicted below, however, user is not accessed in App.js as it is established in SignIn.js, when the Firebase credentials are sent, but I assume it would be similar to this layout?
const App = () => {
var initialRoute = null
React.useEffect(() => {
const unsubscribe = auth().onAuthStateChanged(user => {
if(user) {
initialRoute = "Home"
}
else {
initialRoute = "SignIn"
}
})
return unsubscribe
}, []);
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false,
}}
initialRouteName={initialRoute}
>
The reason it needs to affect the initial route, and not just redirect anyone who reopens to the home page, is because after registration, there are extra steps included that adjust the database, such as location mapping and etc., therefore, the redirection has to occur within the initial route.
Thanks for your assistance.

This is not how you build a navigation flow with react-navigation. But that's no problem since theres a guide here on the official react-navigation side on how to do that: https://reactnavigation.org/docs/auth-flow/

Fixed by setting two different returns within App.js, one for if the user is authenticated with Firebase that sets the initialRoute to "Home", and one for else that sets it to "SignUp", seems to work fine.

Related

Protect the unauthenticated route in next js

I'm newly start on next js project , we have added a middlewate in next js to protect the route like below
useEffect(() => {
if (typeof window !== undefined) {
if (router.pathname == "/reset-password") {
// allow before login
}else if (!loginUser.authenticated) {
router.push('./login')
}
else if (loginUser.authenticated && !loginUser.selectedCustomer) {
router.push('./search-customer')
} else if (loginUser.authenticated && loginUser.selectedCustomer) {
if (router.pathname == "/") {
router.push("/stock-items/categories");
}
}
}
}, []);
return <>{props.children}</>;
But the issue is when any one direclty hit the specific route the controller goes to specific page and then nevigate to login screen if user is not login
i'm trying to stop that type of process , if user is not login then then any route should not be nevigate
please help us
useEffect runs on the client-side after the DOM has rendered (the typeof window check is therefore unnecessary).
This means any checks you place in the useEffect will be ran after the user see's the page (even just for a split second).
There are 2 options how you can handle this. Both are mentioned in the authentication guide in NextJS docs. I recommend taking a look.
Option 1
Set loading state default state as true, rendering a loading indicator while you do your authentication checks...
After you finish authenticating, toggle the loading state to false, allowing to render the page.
Option 2 (Recommended)
What you want to do here is take advantage of Next's core features such as getServerSideProps.
The code you put into getServerSideProps runs BEFORE the client receives any HTML payload.
In the getServerSideProps you can do your authentication, check for authentication cookie or Authorization header, depends what authentication method you use, and either redirect the user to /login page.

Prevent React Native remount of component on context value change

In a chat app I am building I want to deduct credits from a user's account, whenever the users sends a message and when a chat is initiated.
The user account is accessible in the app as a context and uses a snapshot listener on a firestore document to update whenever something changes in the user account document. (See code samples 1. and 2. at the bottom)
Now whenever anything in the userAccount object changes, all of the context providers children (NavigationStructure and all its subcomponents) are re-rendered as per React's documentation.
This, however causes huge problems on the chat screen that also uses this context:
The states that are defined there get re-initalized whenever something in the context changes. For example, I have a flag that indicates whether a modal is visible, default value is visible. When I go onto the chat screen, hide the modal, change a value manually in the firestore database (e.g. deduct credits) the chat screen is rerendered and the modal is visible again. (See code sample 3.)
I am very lost what the best way to solve this issue is, any ideas?
Solutions that I have thought about:
Move the credits counter to a different firestore document and deduct the credits once per day, but that feels like a weird workaround.
From Googling it seems to be possible to do something with useCallback or React.memo, but I am very unsure how.
Give up and become a wood worker...seems like running away from the problem though.
Maybe it has something to the nested react-navigation stack and tab navigators I'm using within NavigationStructure?
Desperate things I have tried:
Wrap all sub-components of NavigationStructrue in "React.memo(..)"
Make sure I don't define a component within another component's body.
Look at loads of stack overflow posts and try to fix things, none have worked.
Code Samples
App setup with context
function App() {
const userData = useUserData();
...
return (
<>
<UserContext.Provider value={{ ...userData }}>
<NavigationStructure />
</UserContext.Provider>
</>
}
useUserData Hook with firestore snapshot listener
export const useUserData = () => {
const [user, loading] = useAuthState(authFB);
const [userAccount, setUserAccount] = useState<userAccount | null>();
const [userLoading, setUserLoading] = useState(true);
useEffect(() => {
...
unsubscribe = onSnapshot(
doc(getFirestore(), firebaseCollection.userAccount, user.uid),
(doc) => {
if (doc.exists()) {
const data = doc.data() as userAccount & firebaseRequirement;
//STACK OVERFLOW COMMENT: data CONTAINES 'credits' FIELD
...
setUserAccount(data);
}
...
}
);
}, [user, loading]);
...
return {
user,
userAccount,
userLoading: userLoading || loading,
};
};
Code Sample: Chat screen with modal
export const Chat = ({ route, navigation }: ChatScreenProps): JSX.Element => {
const ctx = useContext(UserContext);
const userAccount = ctx.userAccount as userAccount;
...
//modal visibility
const [modalVisible, setModalVisible] = useState(true);
// STACK OVERFLOW COMMENT: ISSUE IS HERE.
// FOR SOME REASON THIS STATE GET'S RE-INITALIZED (AS true) WHENEVER
// SOMETHING IN THE userAccount CHANGES.
...
return (
<>
...
<Modal
title={t(tPrefix, 'tasklistModal.title')}
visible={ModalVisible}
onClose={() => setModalVisible(false)}
footer={
...
}
>
....
</Modal>
...
</>)
}
Any change to the context does indeed rerender all consumer components whether they use the changed property or not.
But it will not unmount and mount the component which is the reason why your local state gets initialized to the default value.
So the problem is not the in the rerenders (rarely the case) but rather <Chat ... /> or one of it's parent component unmounting due to changes in the context.
It is hard to tell from the partial code examples given but I would suggest looking at how you use loading. Something like loading ? <div>loading..</div> : <Chat ... /> would cause this behaviour.
As an example here is a codesandbox which illustrates the points made.
This is a characteristic of React Context - any change in value to a context results in a re-render in all of the context's consumers. This is briefly touched on in the Caveats section in their docs, but is expanded on in third-party blogs like this one: How to destroy your app's performance with React Context.
You've already tried the author's suggestion of memoization. Memoizing your components won't prevent re-initialization, since the values in the component do change when you change your user object.
The solution is to use a third-party state management solution that relies not on Context but on its own diffing. Redux, Zustand, and other popular libraries do their own comparison so that only affected components re-render.
Context is really only recommended for values that change infrequently and would require full-app re-renders anyway, like theme changes or language selection. Try replacing it with a "real" state management solution instead.

Deep Link doesn't open the app instead does a google search

I have been using Expo to develop a react-native app, The functionality I am currently trying to implement is to share a link with friends on platforms such as fb messenger/whatapp or even normal texts and when they click this link it will launch my app to a specific page using the parameters.
After extensive research online - I’ve come to a blocker, following expo’s documentation I defined a scheme for my app - when I press share everything works correctly a message is created and I’m able to share content but only as string.
I am using react-natives Share library to share to an app and I’m using Expo to provide me with the link.
Ideally my first goal is to get the app opening using the Expo Link before I explore further into adding more functionality to the link.
Share.share({
message: "Click Here to View More! " + Linking.makeUrl( ' ' , { postkey : "7a5d6w2x9d6s3a28d8d});
url: Linking.makeUrl( ' ' , { pkey : gkey });
title: 'This post is amazing',
})
.then((result) =>{
console.log(result)
if(result === 'dismissedAction'){
return
}
})
.catch((error) => console.log(error))
In the root of my app I have also defined the event handlers: App.js
_handleRedirect=(event)=> {
let {path,queryParams} = Linking.parse(event);
Alert.alert(`queryparams : ${event} path : ${path} `)
this.props.navigation.navigate("Post_Detail",{key:queryParams.postkey})
}
}
componentDidMount() {
let scheme = 'nxet'
Linking.getInitialURL()
.then(url => {
console.log("App.js getInitialURL Triggered")
// this.handleOpenURL({ url });
})
.catch(error => console.error(error));
Linking.addEventListener('url', ({url}) => this._handleRedirect(url));;
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL);
}
When I share the link to Whatsapp, Facebook Messenger or even just messages or notes it appears as myapplink://, I try to enter this into the browser and instead of asking me to open my app - it does a google search.
Please note I am attempting to have this working on Android Device and facing this issue
Is there something I am doing incorrectly?
Any help is much appreciated. Thanks.
You can not open external links, means other than http, https on Android. But you can on iOS. In order to be able to open your expo links, you need proper anchor tags on android. You can create html mails and give it a try, you will see it is gonna work on Android as well.

React Native - How to persist a Firebase authenticated user session from react-native app to a (react-native-webview) webview component?

I have a react-native app which uses Firebase (with "#react-native-firebase/auth": "^6.0.1").
In the app there is one Webview (with "react-native-webview": "^7.5.2").
The user logs in, then I have access to its jwt by using:
auth().currentUser.getIdTokenResult()
.then(jwtToken => {
console.log('jwtToken: ', jwtToken.token);
})
Now I need the user to open the Webview and still being connected because the webview points to my firebase hosting page.
<WebView
source={{
uri: 'my-firebase-hosting-url',
headers: {
Authorization: jwtToken,
},
}}
renderLoading={this.renderLoadingView}
startInLoadingState={true}
sharedCookiesEnabled={true}
thirdPartyCookiesEnabled={true}
domStorageEnabled={true}
ref={ref => {
this.webview = ref;
}}
/>
On the server side, I have setup express and cookie-session.
The user doesn't seems to be logged in the webview: on the server side, when I console.log(req.session) it shows an empty object {}.
I've tried multiple options but nothing seems to work in iOS (not tried on Android yet)
What am I missing? Are there extra steps on the frontend/backend? (I am still new to programming)
Thank you for your help.
I finally solved it and hope it will help others:
Design your state
state = {
shouldRenderWebview: false,
authorization: ''
...
In your constructor, get a refreshed token from firebase
auth()
.currentUser.getIdTokenResult()
.then(result => {
this.setState({
authorization: result.token,
shouldRenderWebview: true,
});
})
Design your main component to either show a loading view (while it's getting the token) or render the webview
Your webview (and add 'Bearer' to the code)
const Authorization = 'Bearer ' + this.state.authorization;
<WebView
source={{
uri: this.state.urlToGo,
headers: {
Authorization,
},
}}
....
In your backend, check if the authentication code is valid
firebase.auth().verifyIdToken(req.headers.authorization.substr(7));
Then do your stuff depending on the result.

How to get currentUser on first load in firebase-auth without requesting

I'm using firebase authentication, and my navbar is depend on if user is logged in or not. When i load the website, if user not logged out before, he should still stay as logged in.But, Firebase onAuthStateChanged listener returns user after 100-300ms, so my navbar can't decide if user is logged in or not.So, navbar shows login and signup button for 300ms, then firebase returns user, and navbar shows dashboard and logout button.
I'm using vue, I've tried saving UID and username on localstorage, but i'm concerning about security. what if someone manually changed localstorage data? Also, user may logged out, but stay logged in my manually setting localStorage.setItem('user', ....)
Navbar.vue
<div v-if="user.uid" class="flex row space-b">
// userpic, photo, logout button
</div>
<div v-else>
// login and signup button
</div>
vuex
state: {
user: {
uid: null,
photoURL: null,
username: null,
},
},
I tried not to mount Vue instance until onAuthStateChanged listener returns something, but this can't be a good solution, hence i'm rendering website nearly 300ms late.
Anyway, i saved it in cookie. when website loads, if cookie is exists i render navbar as if user logged in, and then double check after firebase listener returns
This is caused because beforeEach is getting executed before firebase has fully finished initialization.
You can use onAuthStateChanged observer to make sure the vue app is initialized only after the firebase is fully initialized.
One way to fix it is to wrap the vue initialization code in main.js(new Vue( ... )) with onAuthStateChanged like this:
let app;
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
console.log("user", user);
if (!app) {
new Vue({
router,
vuetify: new Vuetify(options.Vuetify),
render: (h) => h(App),
}).$mount("#app");
}
});
for version 7 firebase you can check this repo file
https://github.com/ErikCH/FirebaseTokenFrontend/blob/master/src/main.js

Resources