Adding Sign In With Apple to a managed Expo app using Firebase Authentication

Dan Singer
5 min readJan 1, 2020

The Expo managed workflow provides great abstractions for easy mobile development, but Social Oauth login brings together a few of these third-party services with very little control or transparency, making those abstractions leaky.

Image of an iPhone

I recently tried to implement Sign In With Apple in managed workflow, but ran into trouble quickly with limited guidance available online. Now that I have it working, I wanted to be sure I made note of my steps for other developers who are trying to comply with the new requirements for including Apple Id login for apps using social login.

Step 0: Avoid implementing Sign In With Apple in Expo

Start with the guidelines (updated 4/2020).

“Apps that use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option.”

If you only offer a username/password login option, there is no need to implement Sign In With Apple.

Also, consider ejecting. If you’re considering ejection soon for other reasons, you can add this to the list. The react-native-firebase library is only available in bare workflow, but has a simple set of steps on their site to get set up with Apple Authentication.

Finally, if you are planning to integrate with any OAuth that is not supported by Firebase out of the box (Google/Facebook/Twitter/Github), you’ll need to implement token validation and use a custom provider anyways, which you can extend for Apple Authentication. You can take a look at custom authentication with Firebase here, and take a look at this fantastic guide for building an OAuth validation server into your architecture.

If you still need to implement Apple Authentication using only the Firebase library in managed Expo, read on. I like to work from front to back so that I have a real way of testing the backend integrations as I build them.

Step 1: Add Sign-In With Apple Button using Expo documentation

First start with the Apple Authentication docs provided by Expo to add only the frontend component, with two important notes:

  • You can skip all but the first “Configuration” step (2–7), as they are incomplete if you’re using Firebase. You should still set ‘ios.usesAppleSignIn’ to ‘true’.
  • The latest Expo SDK accepts a “nonce” value which you’ll need for Firebase to validate the token. The AppleAuthentication SDK requires that the nonce be a valid sha256-hashed value. I used the Expo Crypto library. There is also a “state” value that you can use to prevent CSRF attacks. Neither of these values needs to be cryptographically secure.
import React from "react";
import * as Crypto from "expo-crypto";
import * as AppleAuthentication from "expo-apple-authentication";
export const Login: React.FunctionComponent<> = async () => {const loginWithApple = async () => {
const csrf = Math.random().toString(36).substring(2, 15);
const nonce = Math.random().toString(36).substring(2, 10);
const hashedNonce = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256, nonce);
const appleCredential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL
],
state: csrf,
nonce: hashedNonce
});
const { identityToken, email, state } = appleCredential;
}
// This should go in state
const loginAvailable = await AppleAuthentication.isAvailableAsync();
return (
...
{loginAvailable === true ? (
<View style={{ alignItems: "center" }}>
<AppleAuthentication.AppleAuthenticationButton
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK}
cornerRadius={5}
style={{ width: 250, height: 50 }}
onPress={loginWithApple}
/>
) : null}
...);
};

Step 2: Set up calls to Firebase from React Native

I like to create a test driver by setting up my caller before my “backend” (in this case the backend is all third party tools, we just need to configure them). We’re using the Firebase SDK docs, but skip ahead to this section and scroll to step 2. Even though we’re not using Node, we’ll reuse their code for validating tokens and logging in. Make sure to use the unhashed nonce value from before.

import * as Firebase from "firebase";
import {
FIREBASE_API_KEY,
FIREBASE_AUTH_DOMAIN,
FIREBASE_DATABASE_URL,
FIREBASE_STORAGE_BUCKET
} from "react-native-dotenv";
const FbApi = Firebase.initializeApp(Object.freeze({
apiKey: (FIREBASE_API_KEY as string),
authDomain: (FIREBASE_AUTH_DOMAIN as string),
databaseURL: (FIREBASE_DATABASE_URL as string),
storageBucket: (FIREBASE_STORAGE_BUCKET as string)
});
const loginWithApple = async () => {
... // Get identityToken as above
if (identityToken) {
const provider = new FbApi.auth.OAuthProvider("apple.com");
const credential = provider.credential({
idToken: identityToken,
rawNonce: nonce // nonce value from above
});
await FbApi.auth().signInWithCredential(credential);
}
}

Step 3: Configure Firebase and Apple

Since Firebase will be handling all the token validation with Apple, we’ll use Firebase’s documentation for configuration.

First, work through the Firebase configuration docs here (a lot of these steps also show up in the Expo documentation that we skipped earlier). It’s going to link out to Apple documentation for configuration. A few notes:

  • You may already have an identifier for your app (e.g. for Push Notifications): reuse that if you do
  • In the Apple configuration for “Verify your web domain” I flipped back to the Firebase docs and used their Firebase Hosting guide to get the file uploaded

Now work through the very next section in the Firebase docs (here is a direct link). One note:

  • In step 2, where it says, “Specify the Service ID you created in the previous section”, enter host.exp.Exponent (or, on older Expo versions, host.exp.Expo) in place of your app bundle id. Keep track of your Service ID, you’ll need it when you deploy live (it should be the same as your app bundle ID).

Step 4: Build In Expo / Standalone

You should now be able to open your app in Expo, see the Apple Authentication button in your RN app, and tap it to verify your identity biometrically and login.

When you are ready to build a standalone version, make sure that you update Firebase to use your app’s Service ID. As a reminder, you can find it at Authentication > Sign-In Method > Apple . If you run your app in production with the Expo service ID configured (or vice versa), you will get an error back from Firebase that reads: “The audience in ID Token does not match the expected audience”.

As a note, this means that you can’t have Sign In With Apple work in both development and production on the same Firebase app: once you release a standalone build with real users, you will have to take downtime to test your app in Expo.

Once you’ve updated your Service ID, simply build and deploy to TestFlight!

--

--