Test locally
This page walks through signing in with real Apple accounts from the example app on a desktop browser, a phone browser, an Android device, and an iPhone, all from your own machine. Web and Android identify the app by a Services ID and send Apple to a Return URL, and Apple rejects localhost and IP addresses as domains and Return URLs. A tunnel gives your local server an HTTPS host that Apple accepts, so web and Android go through one. iOS uses the App ID of the signed build instead, so it needs no tunnel, no Services ID and no redirect URI.
What you need
- An Apple Developer account with a web Services ID that has Sign in with Apple enabled, as described in Apple Developer. The examples use
com.example.app.web. cloudflaredfor the tunnel. On macOS, install it withbrew install cloudflared.- For Android, a device with USB debugging turned on, connected over USB.
- For iOS, an iPhone connected over USB, with Developer Mode enabled and the Mac trusted. You also need an App ID in your team that has Sign in with Apple enabled, and you set it as the bundle identifier in Test on an iPhone.
Build the library and the example
The example depends on the library through file:.., and the example's web server imports expo-apple-sign-in/server from the built files, so build the library first. From the repository root:
npm install
npm run build
cd example
npm installOpen a tunnel
Start a Cloudflare quick tunnel that points at port 43181, the port that the example's web server uses. The tunnel can start before the server does.
HOME="$(mktemp -d)" cloudflared tunnel --url http://127.0.0.1:43181The temporary HOME keeps cloudflared from loading ~/.cloudflared/config.yml, which can describe a named tunnel. With the temporary HOME, cloudflared logs that it cannot determine a default configuration path and prints a https://<host> URL on trycloudflare.com within seconds. Keep this terminal open, and note the host. The rest of the page calls it <host>.
Register the tunnel on the Services ID
In the Apple Developer portal, open the web Services ID and edit its Sign in with Apple configuration. Under Domains and Subdomains, add <host>. Under Return URLs, add https://<host>/auth/callback. Save the configuration.
Point the example at the tunnel
Create example/.env with the Services ID and the Return URL:
EXPO_PUBLIC_APPLE_SERVICE_ID=com.example.app.web
EXPO_PUBLIC_APPLE_REDIRECT_URI=https://<host>/auth/callbackThe example uses these two values on web and on Android. iOS ignores them. Expo inlines EXPO_PUBLIC_* values when it bundles, so a change to example/.env only takes effect after a new export or a new build.
Test web
From example/, export the web app and serve it:
npm run web:serveThe script exports the app to example/dist and serves it at http://127.0.0.1:43181. The tunnel forwards https://<host> to that address.
Open https://<host> in a desktop browser and press the button. The example sets webMode: 'auto', so a desktop browser signs in through the popup. Open the same URL on a phone to sign in through the redirect: the tab goes to Apple, Apple posts the result to https://<host>/auth/callback, and the app loads again with the credential. See Web for the two modes.
The server prints one line per request with the method, the path and the status. A redirect sign-in from a phone appears as POST /auth/callback 200 followed by GET / 200.
A successful sign-in replaces the status line with three lines: user: …, email: … and token: N characters.
Test on an Android device
Android needs the Services ID, the Return URL and the same example/.env as web, so complete the steps above first. Then list the connected devices and note the device id:
adb devices -lBuild the release variant and run it on that device:
NODE_ENV=production npx expo run:android --device <id> --variant releaseThe first build takes about five minutes. The command installs app-release.apk and opens the app. The release variant embeds the JavaScript bundle, so the phone needs no Metro server. If your shell exports NODE_ENV, set it to production for release builds, as the command shows.
Press the button in the app. The sign-in works with the same Services ID and the same tunnel Return URL as web. The tunnel server log shows no request from an Android sign-in, because the Android activity reads Apple's form inside the WebView (see What the redirect URL has to serve). On Android the Return URL has to be registered on the Services ID, and nothing has to answer it.
Test on an iPhone
iOS signs in with the App ID of ios.bundleIdentifier, so the Services ID, the redirect URI and the tunnel play no part. The signing team and the bundle identifier come from the native project. An App ID is unique across all Apple teams, so set ios.bundleIdentifier in example/app.json to an App ID in your own team that has Sign in with Apple enabled.
If example/ios already exists from an earlier build, a change to app.json alone does not update it. Set the bundle identifier and the team in Xcode under Signing & Capabilities, or regenerate the project with the following command, which deletes and recreates example/ios:
npx expo prebuild --platform ios --cleanList the connected devices and note the UDID of the iPhone. Device names can repeat, so pass the UDID:
xcrun devicectl list devicesBuild the release configuration and run it on that iPhone:
NODE_ENV=production npx expo run:ios --device <UDID> --configuration ReleaseThe command runs pod install, builds, installs and launches the app. After the install it also starts Metro, which a Release build does not need, so stop it with Ctrl+C. Press the button in the app to sign in.
The iOS Simulator also works when an Apple Account is signed in on it. The iOS Simulator cannot sign in lists the symptom and the fix, and iOS shows the npx expo run:ios commands for it.
Each new tunnel
A quick tunnel gets a new host every time it starts. After a restart, register the new host and the new https://<host>/auth/callback on the Services ID, update both values in example/.env, and export the web app again with npm run web:serve or rebuild the Android app.
Clean up
Stop the web server and the tunnel with Ctrl+C. Removing the old host and Return URL from the Services ID is optional.
Supabase and other providers
The example app does not call Supabase. When you test the Supabase adapter in your own app, Supabase compares the audience of the identity token with the Client IDs of its Apple provider. Tokens from web and Android carry the Services ID, and tokens from iOS carry the App ID, which is the bundle identifier. List both in the Client IDs field, as the Supabase page describes.