Back to Blog
engineeringAuthenticationSupabaseApple Sign InOAuthMobile Development

Fixing Apple Sign In with Hide My Email in Supabase

Users couldn't log in after signing up with Apple. Here's how we debugged a subtle but critical issue with Apple's private relay emails and Supabase authentication.

December 29, 2025
6 min read
Pulore Team
Fixing Apple Sign In with Hide My Email in Supabase

Fixing Apple Sign In with Hide My Email in Supabase

A few weeks ago, users started reporting a frustrating issue: they'd sign up for our client's mobile app, everything would work perfectly, and then the next time they tried to log in — nothing. Account not found.

The kicker? It only happened to some users, and we couldn't reproduce it consistently. Until we realized the pattern: every affected user had signed up with Apple and chosen "Hide My Email."

Here's what went wrong and how we fixed it.

The architecture: why web-only signups

Before diving into the bug, some context on our setup. We were building a mobile app for a client, and we made an early decision: signups would only happen on the web, not in the app.

Why? Apple and Google both take a 30% cut of in-app purchases, and this can extend to subscriptions initiated through their platforms. By handling signups on our web app, we avoid this tax entirely. The mobile app only handles logins.

This meant we needed a custom authentication flow:

  1. User signs up on web (email/password or OAuth with Google/Apple)
  2. We verify the OAuth token server-side
  3. We create the user in Supabase using the admin API
  4. User can then log in on mobile using Supabase's standard OAuth flow

We also disabled Supabase's automatic user creation on OAuth sign-in. Without this, a user could bypass our web signup entirely by just signing in with Apple on the mobile app — Supabase would create a new account automatically, and we'd miss the signup flow.

How it was supposed to work

The flow was straightforward:

Web Signup with Apple:
1. User authenticates with Apple
2. Apple returns ID token with email + sub (unique identifier)
3. We verify the token server-side
4. We create user in Supabase with their email
5. Success ✓

Mobile Login with Apple:
1. User authenticates with Apple
2. Supabase looks for existing user with matching email
3. User logged in ✓

And for 90% of users, this worked perfectly. Google users? No issues. Apple users with regular emails? Fine. Apple users with Hide My Email? Broken.

What is Hide My Email?

When signing in with Apple, users can choose to hide their real email address. Apple generates a unique, random email address like dpdcnf87gc@privaterelay.appleid.com that forwards to their real inbox.

Here's the critical part: this private relay email is unique per app per user. If the same user signs into a different app with Hide My Email, they get a different relay address.

More importantly for our issue: Apple guarantees one thing across sessions — not the email, but the sub claim (subject identifier). This is a stable, unique identifier for that Apple ID within your app's scope.

The bug

We were matching users by email. Here's what happened:

Web Signup:
- User signs up with Apple, chooses Hide My Email
- Apple gives us: email = "abc123@privaterelay.appleid.com"
- We create user in Supabase with this email ✓

Mobile Login (days later):
- User signs in with Apple
- Supabase OAuth looks for user by... what exactly?

Here's where our understanding was wrong. We assumed Supabase would match on email. But with OAuth providers, Supabase actually looks for a linked identity in the auth.identities table — matching on the provider and the provider's user ID (the sub claim).

Because we created users manually with just an email, there was no Apple identity linked. Supabase couldn't find a match, and the login failed.

For regular Apple emails, this was masked because we had email confirmation enabled, and Supabase could fall back to email matching. But for private relay emails, this fallback didn't work reliably.

The fix

The solution was to properly link the Apple identity when creating users. We needed to insert a record into auth.identities that Supabase would recognize during OAuth login.

First, we created a PostgreSQL function in Supabase:

CREATE OR REPLACE FUNCTION link_apple_identity(
  p_user_id UUID,
  p_provider_id TEXT,
  p_email TEXT,
  p_custom_claims JSONB DEFAULT '{}'::jsonb
)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
  INSERT INTO auth.identities (
    user_id,
    provider_id,
    provider,
    identity_data,
    last_sign_in_at,
    created_at,
    updated_at
  ) VALUES (
    p_user_id,
    p_provider_id,
    'apple',
    jsonb_build_object(
      'iss', 'https://appleid.apple.com',
      'sub', p_provider_id,
      'email', p_email,
      'provider_id', p_provider_id,
      'email_verified', true,
      'phone_verified', false,
      'custom_claims', p_custom_claims
    ),
    NOW(),
    NOW(),
    NOW()
  );
END;
$$;

Then, in our signup handler, after creating the user, we decode the Apple token to extract the sub and call this function:

// Decode Apple's ID token to get the payload
const parts = token.split(".");
const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString());
 
// After creating the user with Supabase admin...
if (provider === "apple") {
  await supabase.rpc("link_apple_identity", {
    p_user_id: userId,
    p_provider_id: payload.sub,
    p_email: email,
    p_custom_claims: {
      auth_time: payload.auth_time,
      is_private_email: payload.is_private_email,
    },
  });
}

Now when users log in with Apple on mobile, Supabase finds the linked identity by matching the sub, regardless of what email Apple returns.

Migrating existing users

We also had to fix existing users who were stuck. We wrote a migration script that:

  1. Found all users who signed up with Apple (we tracked this in our database)
  2. For each user, had them re-authenticate with Apple on web
  3. Captured the sub from the new token
  4. Linked the identity using our new function

For users who couldn't or wouldn't re-authenticate, we provided a manual account recovery flow using email verification.

Lessons learned

1. Never rely solely on email for OAuth identity

The sub claim exists for a reason. It's the stable identifier that providers guarantee won't change. Email can change, can be hidden, can be unreliable. Always store and match on the provider's user ID.

2. Test with Hide My Email specifically

It's easy to test OAuth flows with your own account using your real email. But you need to test the edge cases too. Create a test Apple ID, use Hide My Email, and verify the full flow works.

3. Understand your auth library's matching logic

We assumed Supabase matched OAuth users by email. It actually uses the identity table with provider + provider_id. Reading the source code (or at least the docs more carefully) would have caught this earlier.

4. Custom auth flows need custom identity management

When you step outside the standard "let Supabase handle everything" flow, you take on responsibility for things the library normally does automatically. In our case, that meant manually linking OAuth identities.

The takeaway

Apple's Hide My Email is a great privacy feature for users. But it requires developers to handle OAuth properly — using the sub identifier rather than email as the source of truth.

If you're building custom OAuth flows with Supabase (or any auth system), make sure you're linking identities correctly from the start. The email is for communication. The sub is for identity.


Running into authentication edge cases with your app? Get in touch — we've debugged more OAuth flows than we can count.

Pulore Team
Engineering
Share:

Want to discuss this topic?

We love talking about software architecture, development best practices, and technical strategy.