Passwordless Firebase authentication without ejecting

I wanted to use Firebase as an authentication provider for my expo app, using their passwordless email magic link. It’s possible to do this without ejecting your Expo app, but it requires that you set up a small “proxy” server that redirects to an Expo generated deep link in the Firebase completion step.

  1. Create a deep link in Expo.
const expoLink = Linking.makeUrl('your/expo/link');
  1. Create a link to your redirect server that redirects to your Expo link. I’ve provided a Webtask function that you can test this with.
const proxyUrl = `${FIREBASE_LINK_PROXY}?redirectUrl=${encodeURIComponent(expoLink)}`;
  1. Send an email using the Firebase JS SDK, passing along your proxy url.
// import firebase from 'firebase/app';
// import 'firebase/auth';

firebase.auth().sendSignInLinkToEmail(email, {
    handleCodeInApp: true,
    url: proxyUrl
  .then(/* ... */)
  .catch(/* ... */)
// You probably also want to save the email for the completion step
// Here's using Expo's AsyncStorage
AsyncStorage.setItem('@YourApp:unverifiedEmail', email);
  1. Add the proxy to your authorized domains in Firebase.
If you use the Webtask function from above, add the following domain

to this place (replace <YOUR_FIREBASE_PROJECT>)<YOUR_FIREBASE_PROJECT>/authentication/providers
  1. Open the link in the email Firebase sends you—you’ll be sent to your Expo app (or asked if on iOS).
  2. Handle the link in Expo and complete sign-in with the Firebase JS SDK using the link
// The Expo link will be available after successfully
// being redirected into the app.
// Feel free to set this up however you want.
// Here I've put it in componentDidMount()
async componentDidMount() {
  const url = await Linking.getInitialURL();
  if (url) {

  Linking.addEventListener('url', ({ url }) => {
// Some function that handles the url using the Firebase JS SDK
async function handleUrl(url) {
  const isSignInWithEmailLink = firebase.auth().isSignInWithEmailLink(url);
  if (isSignInWithEmailLink) {
    try {
      const email = await AsyncStorage.getItem('@YourApp:unverifiedEmail');
      const result = await firebase.auth().signInWithEmailLink(email, url);

      this.setState({ user: result.user });
    } catch (error) {
  1. You should now have the user object from Firebase. Use it! :fire:

Redirect server code

This is just Node.js. You can adapt it easily to any serverless function provider (Zeit Now, Webtask, AWS Lambda, Azure Functions, Netlify Functions, Firebase Functions, etx…) or you own Node.js server.

const url = require('url');

module.exports = (req, res) => {
  const { query } = url.parse(req.url, true);
  const { redirectUrl, } = query;
  if (redirectUrl) {
    const Location = url.format({ pathname: redirectUrl, query: rest });
    res.writeHead(302, { Location });
    res.end(`Redirecting to ${Location}`);
  } else {
    res.end('You must provide a `redirectUrl` in the query');

Thanks for this! It was just what I was looking for.

I’m struggling to make this work with Expo on Web. Would love some advice.

First, this returns an exp:// link

const expoLink = Linking.makeUrl('your/expo/link');

It can’t be followed on web, so I changed the server code to check the user-agent during redirect, and change the protocol to http if on desktop.

//import functions from 'firebase-functions';
const functions = require('firebase-functions');

import url from 'url';
import express from 'express';
import ua from 'express-mobile-redirect';

const redirectApp = express();


redirectApp.get("*", (req, res) => {
    const { query } = url.parse(req.url, true);
    const { redirectUrl, } = query;

    if (!redirectUrl) {
      res.end('You must provide a `redirectUrl` in the query');

    const parsedUrl = url.parse(redirectUrl);

    // This is the code in question:
    if (!req.is_mobile && !req.is_tablet) {
        parsedUrl.protocol = 'http';

    const Location = url.format(parsedUrl);

    res.writeHead(302, { Location });
    res.end(`Redirecting to ${Location}`);

export const redirect = functions.https.onRequest(redirectApp)

But now, the Firebase JS is not accepting the link as a signin url, and won’t finish the auth:

  if (!firebase.auth().isSignInWithEmailLink(expoUrl)) {
    // Always gets here
  // Never gets here
  return finishSigninWithEmailUrl(email, url);

I thought it might have been because the URL now had a different protocol, so I’ve also tried:

  const expoUrl = url.replace(/https?/, 'exp');
  if (!firebase.auth().isSignInWithEmailLink(expoUrl)) {
    // Still always goes here

Any advice to make this work on Web?

1 Like
  • Linking.makeUrl should return the http://yourwebsiteurl when used on the web
  • You don’t need to use the webtask redirect type intermediary on the web. It’s only needed to redirect to a custom scheme (exp:// for expo client or yourscheme:// in standalone) because firebase’s continueurl can only be a http://, https:// or a dynamic link (pain to setup).

I have the exact same issue as @jordanf98765 .

@ianman2000 I am using expo on iOS, my URL looks something like this:


This keeps failing, I get an [Error: Invalid email link!] from Google when attempting to sign in with this link. I’m not using Firebase Dynamic Links and trying to avoid it.

EDIT: My issue was not related actually, my url was badly formed due to the url.format call in the redirect server. I simply concatenated the strings and it worked perfectly, even with the expo scheme.