Sign in with Apple

Add Sign in with Apple to a SupaHost project: create the App ID, Services ID and signing key, generate the client secret JWT, paste it into the Auth tab, and handle native iOS sign-in.

Sign in with Apple has more moving parts than other providers. Instead of a fixed client secret, Apple has you sign a short-lived JWT with a private key, and that JWT is the "secret" you give Auth. This page walks through each piece in order.

You need a paid Apple Developer Program membership.

What you need from SupaHost

Copy the callback URL from Dashboard → your project → Auth tab → OAuth providers. For project app-one on instance acme:

https://app-one.acme.supahost.space/auth/v1/callback

You also need the host part on its own (app-one.acme.supahost.space) for Apple's domain list. If the project has an active custom domain, the card shows that domain instead; use it in both places.

1. Create or update the App ID

In the Apple Developer portal, go to Certificates, Identifiers & Profiles → Identifiers.

  1. Select your app's App ID (for an iOS app, its bundle ID, such as com.example.recipes), or create one with the + button.
  2. Under Capabilities, enable Sign in with Apple and save.

Even if you only have a website, you need an App ID: the Services ID in the next step hangs off it.

2. Create a Services ID

The Services ID is what Apple calls the client ID for web sign-in.

  1. In Identifiers, click + and choose Services IDs.
  2. Give it a description (users may see it) and an identifier, such as com.example.recipes.web. This identifier is your client ID.
  3. After creating it, open it, tick Sign in with Apple, and click Configure:
    • Primary App ID: the App ID from step 1.
    • Domains and Subdomains: app-one.acme.supahost.space (no https://).
    • Return URLs: https://app-one.acme.supahost.space/auth/v1/callback.
  4. Save, then Continue and Save again.

Apple doesn't accept localhost return URLs. Your local app still works, because the return URL is Auth's callback on SupaHost, not your laptop. After the callback, Auth sends the user on to your redirectTo, which can be http://localhost:3000/... as long as it's in Redirect URLs.

3. Create a signing key

  1. Go to Keys, click +, name the key, tick Sign in with Apple, and click Configure to pick the primary App ID.
  2. Register the key and download the .p8 file. Apple lets you download it once.
  3. Note the Key ID (10 characters) and your Team ID (top right of the portal, also 10 characters).

Store the .p8 file like a password. If it leaks, revoke the key in the portal and make a new one.

4. Generate the client secret JWT

The secret is a JWT signed with ES256 using the .p8 key, with your Team ID as issuer and the Services ID as subject. Apple accepts a lifetime of up to six months. Here's a small Node script using the jose package:

// make-apple-secret.mjs  (run: node make-apple-secret.mjs)
import { readFileSync } from 'node:fs'
import { SignJWT, importPKCS8 } from 'jose'

const TEAM_ID = 'A1B2C3D4E5'
const KEY_ID = 'F6G7H8I9J0'
const SERVICES_ID = 'com.example.recipes.web'

const key = await importPKCS8(readFileSync('./AuthKey_F6G7H8I9J0.p8', 'utf8'), 'ES256')
const now = Math.floor(Date.now() / 1000)

const secret = await new SignJWT({})
  .setProtectedHeader({ alg: 'ES256', kid: KEY_ID })
  .setIssuer(TEAM_ID)
  .setSubject(SERVICES_ID)
  .setAudience('https://appleid.apple.com')
  .setIssuedAt(now)
  .setExpirationTime(now + 180 * 24 * 60 * 60) // 180 days, under Apple's limit
  .sign(key)

console.log(secret)

Run it on your own machine; the .p8 key never needs to leave it.

5. Turn on Apple in SupaHost

  1. Dashboard → your project → Auth tab → OAuth providers, click Apple.
  2. Switch on Enabled.
  3. Services ID: com.example.recipes.web.
  4. Secret key (JWT generated from your .p8 key): paste the JWT from the script.
  5. Click Save Apple.

Add your landing pages to Redirect URLs on the Auth card and click Save Auth settings. Changes apply within a minute.

Put a reminder in your calendar. When the JWT expires, Apple sign-in on the web stops working. Generate a new JWT with the same script and paste it into the secret field before then.

6. Sign in on the web

const { error } = await supabase.auth.signInWithOAuth({
  provider: 'apple',
  options: { redirectTo: `${window.location.origin}/auth/callback` },
})

Native iOS and macOS apps

In a native app, use Apple's AuthenticationServices sheet and pass the resulting identity token to Auth with signInWithIdToken. In Swift:

let credentials = OpenIDConnectCredentials(
  provider: .apple,
  idToken: identityTokenString,
  nonce: rawNonce
)
try await supabase.auth.signInWithIdToken(credentials: credentials)

Auth checks that the token's audience matches a configured client ID. A native token's audience is the App ID (bundle ID), not the Services ID. Upstream Supabase Auth accepts a comma-separated list in the client ID field, with the Services ID first for the web flow: com.example.recipes.web,com.example.recipes.

SupaHost only switches a provider on when both a client ID and a secret are saved, so enter the generated JWT even if your app only uses native sign-in.

The user's name and email

  • Name: Apple's identity token doesn't include the user's name, and the web flow doesn't pass it to Auth. Native apps receive the name once, on the first authorization only. Save it right away with supabase.auth.updateUser({ data: { full_name } }).
  • Hide My Email: users can share a private relay address such as [email protected] instead of their real one. Apple forwards mail to it only from senders you register under Sign in with Apple for Email Communication in the portal. Auth emails on SupaHost come from [email protected]; test delivery to a relay address before you rely on Auth emails for these users.

Troubleshooting

Problem Fix
invalid_client from Apple The JWT expired, or its Team ID, Key ID or Services ID is wrong. Generate a new one
invalid_request / redirect URI error The return URL in the Services ID doesn't match SupaHost's callback, or the domain isn't listed
Web works, native fails (or the reverse) Check the order and contents of the client ID list
Broke after adding a custom domain Add the custom domain and its callback to the Services ID. See OAuth after a custom domain

For mobile setup, see React Native and Expo, Flutter or iOS and Swift.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us