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 FIREBASE_LINK_PROXY = 'https://wt-6e2a5f000b93f69e1b65cf98021e1945-0.sandbox.auth0-extend.com/firebase-authentication-link-redirect';
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

wt-6e2a5f000b93f69e1b65cf98021e1945-0.sandbox.auth0-extend.com

to this place (replace <YOUR_FIREBASE_PROJECT>)

https://console.firebase.google.com/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) {
    this.handleUrl(url);
  }

  Linking.addEventListener('url', ({ url }) => {
    this.handleUrl(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) {
      console.log(error.message);
    }
  }
}
  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, ...rest } = query;
  if (redirectUrl) {
    const Location = url.format({ pathname: redirectUrl, query: rest });
    res.writeHead(302, { Location });
    res.end(`Redirecting to ${Location}`);
  } else {
    res.writeHead(400);
    res.end('You must provide a `redirectUrl` in the query');
  }
};
1 Like

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.use(ua.is_mobile());
redirectApp.use(ua.is_tablet());

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

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

    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
    return
  }
  // 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
    return
  }

Any advice to make this work on Web?