How to setup sendSignInLinkToEmail() from Firebase in react-native? - react-native

Working on a react-native project using #react-native-firebase/app v6 we recently integrated signing in with a "magic link" using auth.sendSignInLinkToEmail
We couldn't find a good example on how to setup everything in react-native and had different problems like
- auth/invalid-dynamic-link-domain - The provided dynamic link domain is not configured or authorized for the current project.
- auth/unauthorized-continue-uri - Domain not whitelisted by project
Searching for information and implementing the "magic link login" I've prepared a guide on how to have this setup in react-native

Firebase project configuration
Open the Firebase console
Prepare firebase instance (Email Link sign-in)
open the Auth section.
On the Sign in method tab, enable the Email/Password provider. Note that email/password sign-in must be enabled to use email link sign-in.
In the same section, enable Email link (passwordless sign-in) sign-in method.
On the Authorized domains tab (just bellow)
Add any domains that will be used
For example the domain for the url from ActionCodeSettings needs to be included here
Configuring Firebase Dynamic Links
For IOS - you need to have an ios app configured - Add an app or specify the following throughout the firebase console
Bundle ID
App Store ID
Apple Developer Team ID
For Android - you just need to have an Android app configured with a package name
Enable Firebase Dynamic Links - open the Dynamic Links section
“Firebase Auth uses Firebase Dynamic Links when sending a link that is meant to be opened in a mobile application.
In order to use this feature, Dynamic Links need to be configured in the Firebase Console.”
(ios only) You can verify that your Firebase project is properly configured to use Dynamic Links in your iOS app by opening
the following URL: https://your_dynamic_links_domain/apple-app-site-association
It should show something like:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "AP_ID123.com.example.app",
"paths": [
"NOT /_/", "/"
]
}
]
}
}
IOS Xcode project configuration for universal links
Open the Xcode project and go to the Info tab create a new URL type to be used for Dynamic Links.
Enter a unique value in Identifier field and set the URL scheme field to be your bundle identifier, which is the default URL scheme used by Dynamic Links.
In the Capabilities tab, enable Associated Domains and add the following to the Associated Domains list: applinks:your_dynamic_links_domain
(!) This should be only the domain - no https:// prefix
Android
Android doesn’t need additional configuration for default or custom domains.
Packages
A working react-native project setup with react-native-firebase is required, this is thoroughly covered in the library own documentation, here are the specific packages we used
Note: using the dynamicLinks package can be replaced with react-native's own Linking module and the code would be almost identical
Exact packages used:
"#react-native-firebase/app": "^6.7.1",
"#react-native-firebase/auth": "^6.7.1",
"#react-native-firebase/dynamic-links": "^6.7.1",
Sending the link to the user email
The module provides a sendSignInLinkToEmail method which accepts an email and action code configuration.
Firebase sends an email with a magic link to the provided email. Following the link has different behavior depending on the action code configuration.
The example below demonstrates how you could setup such a flow within your own application:
EmailLinkSignIn.jsx
import React, { useState } from 'react';
import { Alert, AsyncStorage, Button, TextInput, View } from 'react-native';
import auth from '#react-native-firebase/auth';
const EmailLinkSignIn = () => {
const [email, setEmail] = useState('');
return (
<View>
<TextInput value={email} onChangeText={text => setEmail(text)} />
<Button title="Send login link" onPress={() => sendSignInLink(email)} />
</View>
);
};
const BUNDLE_ID = 'com.example.ios';
const sendSignInLink = async (email) => {
const actionCodeSettings = {
handleCodeInApp: true,
// URL must be whitelisted in the Firebase Console.
url: 'https://www.example.com/magic-link',
iOS: {
bundleId: BUNDLE_ID,
},
android: {
packageName: BUNDLE_ID,
installApp: true,
minimumVersion: '12',
},
};
// Save the email for latter usage
await AsyncStorage.setItem('emailForSignIn', email);
await auth().sendSignInLinkToEmail(email, actionCodeSettings);
Alert.alert(`Login link sent to ${email}`);
/* You can also show a prompt to open the user's mailbox using 'react-native-email-link'
* await openInbox({ title: `Login link sent to ${email}`, message: 'Open my mailbox' }); */
};
export default EmailLinkSignIn;
We're setting handleCodeInApp to true since we want the link from the email to open our app and be handled there. How to configure and handle this is described in the next section.
The url parameter in this case is a fallback in case the link is opened from a desktop or another device that does not
have the app installed - they will be redirected to the provided url and it is a required parameter. It's also required to
have that url's domain whitelisted from Firebase console - Authentication -> Sign in method
You can find more details on the supported options here: ActionCodeSettings
Handling the link inside the app
Native projects needs to be configured so that the app can be launched by an universal link as described
above
You can use the built in Linking API from react-native or the dynamicLinks #react-native-firebase/dynamic-links to intercept and handle the link inside your app
EmailLinkHandler.jsx
import React, { useState, useEffect } from 'react';
import { ActivityIndicator, AsyncStorage, StyleSheet, Text, View } from 'react-native';
import auth from '#react-native-firebase/auth';
import dynamicLinks from '#react-native-firebase/dynamic-links';
const EmailLinkHandler = () => {
const { loading, error } = useEmailLinkEffect();
// Show an overlay with a loading indicator while the email link is processed
if (loading || error) {
return (
<View style={styles.container}>
{Boolean(error) && <Text>{error.message}</Text>}
{loading && <ActivityIndicator />}
</View>
);
}
// Hide otherwise. Or show some content if you are using this as a separate screen
return null;
};
const useEmailLinkEffect = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const handleDynamicLink = async (link) => {
// Check and handle if the link is a email login link
if (auth().isSignInWithEmailLink(link.url)) {
setLoading(true);
try {
// use the email we saved earlier
const email = await AsyncStorage.getItem('emailForSignIn');
await auth().signInWithEmailLink(email, link.url);
/* You can now navigate to your initial authenticated screen
You can also parse the `link.url` and use the `continueurl` param to go to another screen
The `continueurl` would be the `url` passed to the action code settings */
}
catch (e) {
setError(e);
}
finally {
setLoading(false);
}
}
};
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
/* When the app is not running and is launched by a magic link the `onLink`
method won't fire, we can handle the app being launched by a magic link like this */
dynamicLinks().getInitialLink()
.then(link => link && handleDynamicLink(link));
// When the component is unmounted, remove the listener
return () => unsubscribe();
}, []);
return { error, loading };
};
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFill,
backgroundColor: 'rgba(250,250,250,0.33)',
justifyContent: 'center',
alignItems: 'center',
},
});
const App = () => (
<View>
<EmailLinkHandler />
<AppScreens />
</View>
);
You can use the component in the root of your app as in this example
Or you can use it as a separate screen/route - in that case the user should be redirected to it after
the sendSignInLinkToEmail action
Upon successful sign-in, any onAuthStateChanged listeners will trigger with the new authentication state of the user. The result from the signInWithEmailLink can also be used to retrieve information about the user that signed in
Testing the email login link in the simulator
Have the app installed on the running simulator
Go through the flow that will send the magic link to the email
Go to your inbox and copy the link address
Open a terminal and paste the following code
xcrun simctl openurl booted {paste_the_link_here}
This will start the app if it’s not running
It will trigger the onLink hook (if you have a listener for it like above)
References
Deep Linking In React Native Using Firebase Dynamic Links
React Native Firebase - Dynamic Links
React Native Firebase - auth - signInWithEmailLink
firebase.google.com - Passing State In Email Actions
firebase.google.com - Authenticate with Firebase Using Email Link in JavaScript

Related

How to use "promptAsync" from expo-auth-session, old library used in video tutorial was deprecated

I have been following the tinder 2.0 react native tutorial https://youtu.be/qJaFIGjyRms At 1:04:00 he sets the sign in method to: "await Google.logInAsync()" but I have noticed the google app auth library used in the video is now deprecated, I am redirected to use expo auth session instead but I notice there is a slight difference, where they used "await Google.logInAsync()" I must put "promptAsync" instead, when I do this I get the error promptAsync is undefined, I try with google.loginasync and get the same error that it is still undefined, what should I do? screenshot
code:
import React, { createContext, useContext } from 'react'
//import * as Google from 'expo-auth-session/providers/google';
import * as Google from 'expo-google-app-auth';
const AuthContext = createContext({});
const config = {
androidClientId:
'236293699216-9a0nknjdq7ie79h40iubg0tddokgogfv.apps.googleusercontent.com',
iosClientId:
'236293699216-6jdpm0rd6kn5d0qlbh1vgva5afgbqgib.apps.googleusercontent.com',
scopes: ["profile", "email"],
permissions: ["public_profile","email", "gender", "location"],
}
export const AuthProvider = ({ children}) => {
const signInWithGoogle = async() => {
await Google.logInAsync(config).then(async (logInResult) => {
if (logInResult.type === "success") {
// login
}
});
};
return (
<AuthContext.Provider
value={{
user: null,
signInWithGoogle
}}
>
{children}
</AuthContext.Provider>
)
}
export default function useAuth() {
return useContext(AuthContext);
}
I sought help on the forum that belongs to the maker of the video and other people had come across the same issue, one person recommended to go into package.json find the installed dependencies and change “expo-google-app-auth” from “^10.0.0” to “~9.0.0” and then npm I in the terminal, I have done this and I'm now getting the error “no such file or directory /Users/shangefagan/twinder-3/node_modules/expo-google-app-auth/node-modules/react-native/package.json” I have changed it back to “^10.0.0” but still getting the same error, screenshot
do I just npm uninstall expo-google-app-auth and try to use expo-auth-session as I was originally trying? if so What is the correct way to use promptAsync from the expo-auth-session library
I check the docs for both libraries, expo google app auth: https://docs.expo.dev/versions/v43.0.0/sdk/google/ and expo auth session: https://docs.expo.dev/versions/latest/sdk/auth-session/ but I am unsure exactly how to use the new login method "promptAsync"
To use promptAsync you have to use the package expo-auth-session. Like you said expo-google-app-auth is deprecated.

Implementing app tracking transparency permission request on Expo 45

I have a managed-expo (sdk 45) application and for some reason apple rejects my application because they are unable to locate the App Tracking Transparency permission request. I know it has something to do with expo-ads-admob. I am getting this message:
Please explain where we can find the App Tracking Transparency permission request in your app. The request should appear before any data is collected that could be used to track the user.
If you've implemented App Tracking Transparency but the permission request is not appearing on devices running the latest OS, please review the available documentation and confirm App Tracking Transparency has been correctly implemented.If your app does not track users, update your app privacy information in App Store Connect to undeclare tracking. You must have the Account Holder or Admin role to update app privacy information.
I'm using expo-ads-admob and this is how I did it (docs):
I installed expo-tracking-transparency
and added the function on the first part of the application (App.tsx)
import * as React from "react";
import { requestTrackingPermissionsAsync } from "expo-tracking-transparency";
export default function App() {
React.useEffect(() => {
(async () => {
const { status } = await requestTrackingPermissionsAsync();
if (status === "granted") {
console.log("Yay! I have user permission to track data");
}
})();
}, []);
return (
<MainApplication />
);
}
I also added
"plugins": [
[
"expo-tracking-transparency",
{
"userTrackingPermission": "This identifier will be used to deliver personalized ads to you."
}
]
]
To app.json
Then I use the component (everywhere in the app) like this:
<AdMobBanner
bannerSize={
Platform.OS === "ios" ? "fullBanner" : "smartBannerLandscape"
}
adUnitID={
Platform.OS == "ios"
? "IOS_ADMOB_CODE"
: "ANDROID_ADMOB_CODE"
}
servePersonalizedAds={false}
onDidFailToReceiveAdWithError={() => {}}
onAdViewDidReceiveAd={() => {}}
/>
It works, but iOS keep rejecting my application claiming they can't find the permission. I looked everywhere and saw this is the right way to implement this but unfortunately it didn't work.
Thanks in advance!
if you dont have infoplist go to and add the Description here.
enter image description here

Firebase Google Sign In not working in React Native

import firebase from 'firebase';
import React from 'react';
import { useAuthState } from 'react-firebase-hooks/auth';
import App from './testlogin';
import { View, Text, Button } from 'react-native';
import { firebaseConfig } from './firebaseConfig';
const app = firebase.initializeApp(firebaseConfig);
const auth = app.auth();
const db = app.firestore();
const googleProvider = new firebase.auth.GoogleAuthProvider()
export const signInWithGoogle = async () => {
try {
const res = await auth.signInWithPopup(googleProvider);
const user = res.user;
const query = await db
.collection("users")
.where("uid", "==", user.uid)
.get();
if (query.docs.length === 0) {
await db.collection("users").add({
uid: user.uid,
name: user.displayName,
authProvider: "google",
email: user.email,
});
}
} catch (err) {
console.error(err);
alert(err.message);
}
};
This code accesses the google sign in method and should pop up a window, but I don't get anything when I click the button. I have been having difficulty with implementing Firebase in React Native and this is one of the examples. I need an example of easy Google sign in button in React Native.
I recommend you to use https://rnfirebase.io/ library (their docs are quite helpful).
Here are their instructions for Google Sign-in: https://rnfirebase.io/auth/social-auth#google.
According to them, you need to
ensure the "Google" sign-in provider is enabled on the Firebase Console.
also install #react-native-google-signin/google-signin',
then "Before triggering a sign-in request, you must initialize the Google SDK using your any required scopes and the webClientId, which can be found in the android/app/google-services.json"
Finally, as because following:
Starting April 2020, all existing applications using external 3rd party login services (such as Facebook, Twitter, Google etc) must ensure that Apple Sign-In is also provided. - You would also need to support iOS / and Apple Sign-in too. (if are building app also for iOS).
NOTE: I also remember that I had to put both, SHA1 and SHA256 hashes in the Firebase Console as some Firebase services were not working without it.
How to find SHA hashes in Android for Firebase (signing with Keystore that will be used in production should be already configured):
The debug signing certificate is optional to use Firebase with your app, but is required for Dynamic Links, Invites and Phone Authentication. To generate a certificate run
cd android && ./gradlew signingReport
and copy the SHA1 and SHA256 from the debug key. This generates two variant keys. You can copy the 'SHA1' that belongs to the debugAndroidTest variant key option.
Again, I recommend you to put both SHA hashes in Firebase Console.

React Native App Authentication with Instagram API

I've been trying to build a react native app that requires users to authenticate with their Instagram account. The Instagram API has a authorisation link and perhaps the only way to display that in an app would be through 'WebView' and so I used that.
The authentication workflow runs smoothly and then my server even gets the access token and user-id. But the problem is how to send this access token back to the app? I've used express-js for the 'redirect-uri' and so the WebView makes request to app.get() handler. In order to send response to same client on which the connection is opened, we must use res.send(). This would send the response to WebView, let's say I capture that using 'injectedJavaScript' but this javascript runs within WebView and so its unable to access react-native variables. In the event of a correct access-token, how would I ever navigate away from the WebView?
Any solutions to the above problems would be greatly appreciated. I suspect that there might even be problems with this approach(in my choice of WebView for this purpose, etc.), so a change of approach even entirely would also be of help. All I want is to authenticate the app users with Instagram and get my project going. Thanks a lot.
If you are using Expo, you can use AuthSessions to accomplish this (https://docs.expo.io/versions/latest/sdk/auth-session). The exact way to do it depends on whether you are using a managed workflow or a bare workflow, etc., but for managed workflow you can do the following:
Go to the Facebook Developer's console, go to your app, and add the Instagram Basic Display product.
Under Instagram Basic Display, under Valid OAuth Redirect URIs, use https://auth.expo.io/#your-expo-username/your-project-slug (project slug is in your app.json file)
On the same FB Developer page, add an Instagram tester profile and then follow the steps to authenticate that user.
In your project, install expo install expo-auth-session and import it into your Component
Also install expo-web-browser
Code your component like so:
import React, { useEffect } from 'react';
import { Button, Platform, Text, TouchableOpacity, View } from 'react-native';
import * as WebBrowser from 'expo-web-browser';
import { useAuthRequest, makeRedirectUri } from 'expo-auth-session';
WebBrowser.maybeCompleteAuthSession(); // <-- will close web window after authentication
const useProxy = Platform.select({ web: false, default: true });
const client_id = 9999999999999;
const redirect_uri = "https://auth.expo.io/#your-expo-username/your-project-slug";
const scope = "user_profile,user_media";
const site = "https://api.instagram.com/oauth/authorize?client_id=" + client_id + "&redirect_uri=" + redirect_uri + "&scope=" + scope + "&response_type=code&state=1";
const discovery = { authorizationEndpoint: site }
const GetInstagram = () => {
const [request, response, promptAsync] = useAuthRequest({
redirectUri: makeRedirectUri({
useProxy,
native: redirect_uri
}),
scopes: [scope],
clientId: client_id
}, discovery);
useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params; <--- the IG code will be returned here
console.log("code : ", code);
}
}, [response]);
return (
<View>
<TouchableOpacity onPress={ () => promptAsync({useProxy,windowFeatures: { width: 700, height: 600 }}) }>
<Text>Connect Your Instagram</Text>
</TouchableOpacity>
</View>
)
}
export default GetInstagram;
One way to accomplish this is via using deeplink. I don't think it's the best practice though.
The response from the WebView will be sent to the redirect URL you've previously setup after successful authentication. Please set the redirect URL to your app. For example, on iOS if you have URL Scheme as "myapp123" then, anytime you open your browser and type myapp123://.. it will open your app and your app should be able to get response sent from the instagram.

react native share in a single application

In my react-native app, I want to share a text message with a specific application, e.g whatsapp or texting application without having to first land on the dialog with all the social applications.
For instance if I press the share button and whatsapp is called directly.
I tried using react-native-share but it seems to not be working anymore.
You can use Linking, which gives you a general interface to interact with both incoming and outgoing app links.
For example:
import React, { Component } from 'react';
import { Linking, Button } from 'react-native';
export class App extends Component {
render() {
return <Button
onPress={() => {
let url = 'whatsapp://send?text=Hola Mundo';
Linking.openURL(url).then((data) => {
console.log('open whatsapp')
}).catch(() => {
console.log('App not installed')
});
}}
title="Whatsapp"
color="#4FBE3C"/>;
}
}
For Android, the React Native Share module uses the default ACTION_SEND android intent:
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
In order to have a different behavior, you need either write our own RN plugin that would talk to the app you want it to (if such feature is available) or find a similar plugin on npm.
I assume your plugin should do something like this:
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
sendIntent.setType("text/plain");
startActivity(sendIntent);
sendIntent.setPackage("com.whatsapp");