DEV Community

Anand Rathnas
Anand Rathnas

Posted on • Originally published at jo4.io

Publishing an Expo App to the App Store: The Parts Nobody Warns You About

This article was originally published on Jo4 Blog.

"Just run eas build --platform ios --auto-submit and you're done!"

Famous last words.

The Problem

I had a React Native app built with Expo. Android was already live on the Play Store. iOS should be the same process, right?

Here's what actually happened over the next 4 hours.

Part 1: The Provisioning Profile Dance

First build attempt:

❌ Provisioning profile doesn't support the App Groups capability
Enter fullscreen mode Exit fullscreen mode

My app has a Share Extension (for sharing URLs from other apps). Share Extensions need their own bundle ID, their own provisioning profile, and their own set of capabilities.

The fix:

  1. Go to Apple Developer Portal → Identifiers
  2. Find both your main app ID AND the ShareExtension ID
  3. Enable "App Groups" capability on BOTH
  4. Go to Profiles → Delete the old profiles
  5. Let EAS regenerate them

EAS will ask to create new profiles. Say yes. It knows what it's doing (mostly).

Part 2: Sign in with Apple - The Checkbox That Wasn't

Apple requires apps with third-party login to also offer Sign in with Apple. My app uses Auth0, which supports Apple auth. Should be simple.

Build attempt #2:

❌ Disabled: Sign In with Apple
Enter fullscreen mode Exit fullscreen mode

The app.config.js was missing one line:

ios: {
  // ... other config
  usesAppleSignIn: true  // This one. This is the line.
}
Enter fullscreen mode Exit fullscreen mode

Added it, rebuilt. Profile regenerated with the capability. Build succeeded.

Part 3: The "invalid_client" Mystery

App built. App ran. Tapped "Sign in with Apple."

invalid_client
Enter fullscreen mode Exit fullscreen mode

Checked Auth0 config. Everything looked right:

  • Client ID: io.jo4.mobile
  • Team ID: correct ✓
  • Key ID: correct ✓
  • Private key: pasted from .p8 file ✓

Spent 30 minutes rechecking these values.

Turns out, the Apple Sign in Key I created had empty "Enabled Services". The key existed but wasn't actually configured for Sign in with Apple.

The fix:

  1. Apple Developer → Keys → Click your key → Edit
  2. Check "Sign in with Apple"
  3. Click Configure → Select your app as Primary App ID
  4. Save

Tried again:

invalid_request
Invalid client id or web redirect url
Enter fullscreen mode Exit fullscreen mode

Different error. Progress.

Part 4: Services ID - The Thing Auth0 Docs Bury

Here's what I didn't understand: Auth0 uses a web-based OAuth flow for Apple Sign in (via Universal Login). This means Apple sees it as a web app, not a native app.

Web apps need a Services ID, not just an App ID.

The fix:

  1. Apple Developer → Identifiers → Create Services ID
  2. Name it something like io.jo4.mobile.auth0
  3. Enable "Sign in with Apple"
  4. Configure with:

    • Primary App ID: Your main app
    • Domains: your-tenant.us.auth0.com
    • Return URLs: https://your-tenant.us.auth0.com/login/callback
  5. Update Auth0's Apple connection to use the Services ID as the Client ID

Finally:

✅ Sign in with Apple works
Enter fullscreen mode Exit fullscreen mode

The Complete Checklist

For anyone doing this in the future:

Apple Developer Portal

  • [ ] App ID has "Sign in with Apple" capability
  • [ ] App ID has "App Groups" capability (if using extensions)
  • [ ] ShareExtension ID has matching capabilities
  • [ ] Key created with "Sign in with Apple" enabled
  • [ ] Key configured with correct Primary App ID
  • [ ] Services ID created (for Auth0/web-based flows)
  • [ ] Services ID configured with Auth0 domain and callback URL

app.config.js (Expo)

ios: {
  bundleIdentifier: "your.bundle.id",
  usesAppleSignIn: true,
  entitlements: {
    "com.apple.security.application-groups": [
      "group.your.bundle.id"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Auth0

  • [ ] Apple connection uses Services ID as Client ID (not App ID)
  • [ ] Team ID, Key ID, and private key are correct

EAS

  • [ ] Delete old provisioning profiles if capabilities changed
  • [ ] Let EAS regenerate profiles with new capabilities

Lessons Learned

  1. EAS is magic, until it isn't. The happy path is genuinely one command. The unhappy path is a maze of Apple portals.

  2. Capabilities cascade. If your main app needs a capability, your extensions probably do too.

  3. Auth0 + Apple = Services ID. This isn't obvious from either company's docs. Web-based OAuth flows need a Services ID, not an App ID.

  4. Apple's error messages lie. "invalid_client" can mean the key isn't configured. "invalid_request" can mean you need a Services ID. Neither error tells you this.

  5. The App Store submission is the easy part. Once EAS builds successfully with --auto-submit, it just... works. The hard part is getting that first successful build.


Building a mobile app? Save yourself the debugging session and bookmark this checklist.

Building jo4.io - a URL shortener with a mobile app that now actually exists on the App Store.

Top comments (0)