# Apple Sign-In

> Step-by-step guide to configuring Apple as an OAuth provider for the Convex Auth starter — the App ID, the Services ID, the signing key, and the JWT client secret you have to generate and rotate.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/convex/apple
- Markdown: https://ui.ahmedbna.com/docs/convex/apple.md

---

> Apple requires a deployed, HTTPS callback — your Convex deployment's `.site`
> URL already qualifies, so there is no local-testing workaround to reach for
> here the way there might be with other providers.

## Before you start

- A paid [Apple Developer](https://developer.apple.com) account. Sign in with
  Apple is not available on a free account.
- A Convex deployment, so you have an HTTP Actions URL to point Apple at.
- Two identifiers, not one. Apple needs an **App ID** _and_ a **Services ID**,
  and it is the Services ID — not the App ID — that becomes your
  `AUTH_APPLE_ID`. Getting these backwards is the most common failure here.

## Configure Apple

**1.** Create an App ID

1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list).
2. Select **Identifiers** in the sidebar, make sure **App IDs** is selected in
   the dropdown on the right, and click the **+** button.
3. On the Register a New Identifier page, keep **App IDs** selected and click
   **Continue**.
4. With **App** selected, click **Continue**.
5. Fill in **Description**, and set **Bundle ID** to **Explicit** with your
   app's identifier — e.g. `com.yourcompany.yourapp`. It must match the
   `ios.bundleIdentifier` in your `app.json`.
6. Scroll to **Capabilities** and check **Sign In with Apple**.
7. Click **Continue**, then **Register**.

**2.** Create a Services ID

1. Back on Certificates, Identifiers & Profiles, switch the dropdown to
   **Services IDs** and click **+**.
2. Keep **Services IDs** selected and click **Continue**.
3. Fill in **Description** and **Identifier** — e.g.
   `com.yourcompany.yourapp.service`. This identifier is what you will set as
   `AUTH_APPLE_ID`, so it must be different from the App ID above.
4. Click **Continue**, then **Register**.

**3.** Create a signing key

1. Click **Keys** in the sidebar, then **+**.
2. Give the key a name.
3. Check **Sign In with Apple** and click **Configure** beside it.
4. Select the **App ID** you created as the Primary App ID, and click **Save**.
5. Click **Continue**, then **Register**.
6. Download the `.p8` file.
7. Note the **Key ID** shown next to it, and your **Team ID** from the top
   right of the developer portal.

> Apple lets you download a signing key exactly once. Store the `.p8` somewhere
> safe and out of version control — if you lose it you have to revoke the key
> and create a new one.

**4.** Find your Convex HTTP Actions URL

In the Convex dashboard, open **Settings → URL & Deploy Key** and copy the
**HTTP Actions URL**. It ends in `.site`, not `.cloud` — the `.cloud` URL is
what your app talks to for queries and mutations, and it is not what Apple
redirects to.

**5.** Configure the Services ID for web authentication

1. Return to **Identifiers**, switch the dropdown to **Services IDs**, and
   click the Services ID you created.

2. Make sure **Sign In with Apple** is checked and click **Configure**.

3. Set the **Primary App ID** to your App ID.

4. In **Domains and Subdomains**, enter just the domain portion of your HTTP
   Actions URL — no scheme, no path:

   ```
   fast-horse-123.convex.site
   ```

5. In **Return URLs**, enter the full callback URL:

   ```
   https://fast-horse-123.convex.site/api/auth/callback/apple
   ```

6. Click **Next**, confirm the values, and click **Done**.

7. Back on the Services ID page, click **Continue**, then **Save**.

**6.** Generate the JWT client secret

Apple does not issue a static secret. You sign one yourself with the `.p8`
key, using four pieces of information:

| Value           | Where it comes from                                    |
| --------------- | ------------------------------------------------------ |
| **Team ID**     | Top right of the Apple Developer portal, 10 characters |
| **Services ID** | The identifier from step 2, e.g. `com.you.app.service` |
| **Key ID**      | In the filename of the key, `AuthKey_XXXXXXXXXX.p8`    |
| **Private key** | The contents of the `.p8` file                         |

```js
const jwt = require('jsonwebtoken');
const fs = require('fs');

const privateKey = fs.readFileSync('AuthKey_XXXXXXXXXX.p8');

const token = jwt.sign(
  {
    iss: 'YOUR_TEAM_ID',
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 15777000, // 6 months
    aud: 'https://appleid.apple.com',
    sub: 'YOUR_SERVICE_ID',
  },
  privateKey,
  { algorithm: 'ES256', header: { kid: 'YOUR_KEY_ID' } }
);

console.log(token);
```

Sign it locally. The `.p8` is a private key: pasting it into an online JWT
generator hands whoever runs that page the ability to authenticate as your app.

**7.** Set the environment variables

```bash
npx convex env set AUTH_APPLE_ID your_service_id
npx convex env set AUTH_APPLE_SECRET your_generated_jwt
```

Or add them in the Convex dashboard under **Settings → Environment
Variables**. Set them on your production deployment too, with `--prod`.

> Six months, maximum. When it lapses, Apple sign-in stops for everyone with no
> code change and no deploy to blame. Put a calendar reminder on it the day you
> set it up.

## Test it

```bash
npx expo start
```

Tap **Login with Apple**. The browser sheet opens; sign in with an Apple ID.

## What Apple sends back

Less than Google, and only once. `convex/auth.ts`'s `profile` callback reads
whatever is present on that first authorization:

```ts title="convex/auth.ts"
Apple({
  profile: (appleInfo) => {
    const name = appleInfo.user
      ? `${appleInfo.user.name.firstName} ${appleInfo.user.name.lastName}`
      : undefined;

    return {
      id: appleInfo.sub,
      name: name,
      email: appleInfo.email,
    };
  },
}),
```

`appleInfo.user` — the name — is only present on the very first sign-in.
Every subsequent one omits it, so `name` in the returned profile is
`undefined` from then on. If the user picked **Hide My Email**, `email` is a
private relay address, not their real one. Capture whatever you need at
first sign-in; do not build anything that depends on re-reading it later.

## When it does not work

| What you see                             | Usually                                                                                                                   |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `invalid_client`                         | `AUTH_APPLE_ID` is the App ID instead of the Services ID                                                                  |
| `invalid_client` after months of working | The six-month JWT secret expired                                                                                          |
| Redirect rejected                        | Return URL in the Services ID doesn't match `/api/auth/callback/apple`                                                    |
| Browser closes, nothing happens          | `EXPO_URL`/`SITE_URL` don't cover the redirect — see [the redirect allow-list](/docs/convex/auth#the-redirect-allow-list) |
| Name is missing after the first sign-in  | Working as intended — Apple sends it once                                                                                 |

## Next

- [Google](/docs/convex/google) · [Resend](/docs/convex/resend)
- [Authentication](/docs/convex/auth)
- [Convex Auth Apple provider docs](https://labs.convex.dev/auth/config/oauth/apple)
