Integrating Magic Links with Firebase and React Native
Introduction
In a mobile project I am a part of, we recently transitioned from traditional email and password authentication to a magic link system using Firebase.
This article explores how I implemented email magic links in the project using Firebase with React Native.
Getting started
To begin, we want to set up the Firebase console to enable email link sign-in:
- Open the Firebase console
-
Navigate to the Authentication section.
- Under 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 the Email link (passwordless sign-in) method.
a little bit of trivia, this is the firebase project I used in the app that got me into Toptal. that's what I now use for all my firebase testing
- Under the Settings tab, and under the Authorized domains section:
- Add the domains for the
url
in the magic link setup, or use the default domains,project_name.web.app
.
Configuring Firebase Dynamic Links
I assume you'd already have your React Native project up and running already.
We want to use the values from our app, and add to the Firebase console.
-
For iOS, add your app with the following details on the Firebase console:
- Bundle ID
- Apple Developer Team ID
- For Android - you just need to have an Android app configured with a package name
- Next, we want to enable Firebase dynamic links, enter the domain you want to use. In this case, I'm using:
edwardsmosesapp.page.link
Setting up our React Native project
We want to setup our Xcode project configuration for the firebase universal links.
-
Open the Xcode project, and in the Capabilities tab, enable Associated Domains
- Add the following to the associated domains list: your dynamic links domain, mine is
edwardsmosesapp.page.link
- Add the following to the associated domains list: your dynamic links domain, mine is
Installing Packages
You can find more information on this in the library's documentation (https://rnfirebase.io).
First, we want to install:
yarn add @react-native-firebase/app
Then install the authentication package (https://rnfirebase.io/auth/usage):
yarn add @react-native-firebase/auth
Then finally install the dynamic links package (https://rnfirebase.io/dynamic-links/usage)
yarn add @react-native-firebase/dynamic-links
Also install the below for async storage:
yarn add @react-native-async-storage/async-storage
After which, we'd run to install :
npx pod install
When running the above command, if you run into the below error: you can follow the steps in this section: https://rnfirebase.io/#altering-cocoapods-to-use-frameworks guess who ran into the above error
If you haven't yet configured your Firebase project, follow the below steps in the documentation: https://rnfirebase.io/#generating-ios-credentials
Sending the Magic link
To send a magic link to the user’s email, we want to use the sendSignInLinkToEmail
method.
import React, { useState, useEffect } from "react";
import {
StyleSheet,
Text,
View,
Alert,
TextInput,
Button,
ActivityIndicator,
} from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import auth from "@react-native-firebase/auth";
import dynamicLinks from "@react-native-firebase/dynamic-links";
const MagicLinkLogin = () => {
const [email, setEmail] = useState("");
const BUNDLE_ID =
"org.reactjs.native.example.reactnative-firebase-magiclink-app";
const sendSignInLink = async (email: string) => {
try {
await AsyncStorage.setItem("emailForSignIn", email);
await auth().sendSignInLinkToEmail(email, {
handleCodeInApp: true,
url: "https://bike-rentals-5f360.web.app",
iOS: {
bundleId: BUNDLE_ID,
},
android: {
packageName: BUNDLE_ID,
},
});
Alert.alert(`Login link sent to ${email}`);
} catch (error) {
console.error(error);
Alert.alert("Error sending login link");
}
};
return (
<View style={styles.authContainer}>
<Text style={styles.sectionTitle}>Magic Link Sign In</Text>
<TextInput
style={styles.textInput}
value={email}
onChangeText={(text) => setEmail(text)}
/>
<Button
color={"007AFF"}
title="Send login link"
onPress={() => sendSignInLink(email)}
/>
</View>
);
};
- We're setting
handleCodeInApp
to true since we want the link from the email to open our app and be handled there. - More details on supported options can be found here.
Then, use it in our App.js
:
function App(): React.JSX.Element {
const isDarkMode = useColorScheme() === "dark";
const backgroundStyle = {
backgroundColor: "lightgray",
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? "light-content" : "dark-content"}
backgroundColor={backgroundStyle.backgroundColor}
/>
<View style={styles.sectionContainer}>
<MagicLinkLogin />
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
sectionContainer: {
paddingHorizontal: 24,
height: "100%",
},
authContainer: {
flex: 1,
justifyContent: "center",
},
loadingContainer: {
backgroundColor: "rgba(250,250,250,0.33)",
justifyContent: "center",
alignItems: "center",
},
textInput: {
borderColor: "blue",
borderWidth: 1,
marginTop: 16,
backgroundColor: "white",
padding: 10,
borderRadius: 8,
fontSize: 16,
},
sectionTitle: {
fontSize: 24,
fontWeight: "600",
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: "400",
},
highlight: {
fontWeight: "700",
},
});
Here's our how our app looks: definitely not winning any awards for the aesthetics on this app
If you have everything configured sucessfully, you should get a link in your email. guess how I'm spending my weekend.
Handling the Link Inside the App
We want to use the @react-native-firebase/dynamic-links
package to handle the link inside our app.
const useEmailLinkEffect = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const handleDynamicLink = async (link: any) => {
console.log('what is link', link);
if (auth().isSignInWithEmailLink(link.url)) {
setLoading(true);
try {
const email = await AsyncStorage.getItem('emailForSignIn');
await auth().signInWithEmailLink(email as string, link.url);
} catch (e: any) {
setError(e);
} finally {
setLoading(false);
}
}
};
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
dynamicLinks()
.getInitialLink()
.then(link => link && handleDynamicLink(link));
return () => unsubscribe();
}, []);
return {error, loading};
};
const EmailLinkHandler = () => {
const {loading, error} = useEmailLinkEffect();
if (loading || error) {
return (
<View style={[StyleSheet.absoluteFillObject, styles.loadingContainer]}>
{loading && <ActivityIndicator />}
</View>
);
}
return null;
};
Handling the successful sign-in
When the user successfully signs in, we want to use the onAuthStateChanged
listener to monitor the
authentication state of the user.
const MagicLinkSignIn = () => {
// Set an initializing state whilst Firebase connects
const [initializing, setInitializing] = useState(true);
const [user, setUser] = (useState < FirebaseAuthTypes.User) | (null > null);
// Handle user state changes
useEffect(() => {
const subscriber = auth().onAuthStateChanged((authUser) => {
setUser(authUser);
if (initializing) {
setInitializing(false);
}
});
return () => subscriber(); // unsubscribe on unmount
}, [initializing]);
if (initializing) {
return null;
}
if (!user) {
return <MagicLinkLogin />;
}
return (
<View style={styles.authContainer}>
<Text style={styles.sectionTitle}>Welcome {user.email}</Text>
</View>
);
};
Then, let's update our App.js
:
function App(): React.JSX.Element {
const isDarkMode = useColorScheme() === "dark";
const backgroundStyle = {
backgroundColor: "lightgray",
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? "light-content" : "dark-content"}
backgroundColor={backgroundStyle.backgroundColor}
/>
<View style={styles.sectionContainer}>
<EmailLinkHandler />
<MagicLinkSignIn />
</View>
</SafeAreaView>
);
}
Testing
We can test the integration by simulating link handling in our simulator:
- Copy the link address from your email, and paste the below into the terminal to open the app:
xcrun simctl openurl booted {paste_the_link_here}
And here we have it, the user is automatically signed into the app: interesting coincidence: what's the similarity between both screenshots. hint: peep the minute
Conclusion
This setup allows users to sign into your React Native app using Firebase magic links.
Here's the accompanying GitHub repo for this article: https://github.com/edwardsmoses/integrating-magic-links-with-firebase-react-native
Notes:
Firebase has announced the shutdown of their dynamic links service, but they will continue to support email link authentication; see Firebase FAQ on the topic.
Edwards Moses
Web & Mobile — React & React Native Consultant
I'm Edwards, based in Lagos, Nigeria.
Freelancer Software Developer — collaborating with teams to craft extraordinary products.
From conception through to completion, I find immense joy in witnessing the evolution of an idea into a fully realized product in the hands of users. Check out my projects and articles to see what I've been up to lately.
Ready to bring your ideas to life?
Comments