Apple sign-in
Let people sign in with their Apple Account, on your website and in your iOS apps. This page starts from nothing. You need a paid Apple Developer Program membership (a yearly fee), and about 30 minutes.
If your iOS app offers Google sign-in, the App Store generally requires Sign in with Apple as well.
What you'll end up with#
| Variable | Required? | Secret? | Looks like | How you get it |
|---|---|---|---|---|
APPLE_TEAM_ID |
Yes | No | ABCDE12345 |
Copied from your Apple Developer account |
APPLE_KEY_ID |
Yes | No | XYZ987WVU6 |
Copied from Apple, when you create the key |
APPLE_PRIVATE_KEY_FILE |
Yes (or APPLE_PRIVATE_KEY) |
Yes, the file it points to | /run/secrets/AuthKey_XYZ987WVU6.p8 |
A .p8 file you download from Apple, once |
APPLE_SERVICES_ID |
For sign-in on websites | No | com.example.web |
You choose it, and register it at Apple |
APPLE_BUNDLE_IDS |
For sign-in inside iOS apps | No | com.example.app |
Your iOS app's bundle ID |
APP_PUBLIC_URL |
For websites | No | https://api.example.com |
Your API's own https address |
You need at least one of APPLE_SERVICES_ID and APPLE_BUNDLE_IDS, and all of the first three.
There is no Apple client secret to create. Apple's own guides tell you to make a "client secret" by signing a token with your key and to renew it every few months. Your API does this for you: each time it talks to Apple, it creates a fresh one from APPLE_TEAM_ID, APPLE_KEY_ID and the .p8 key, valid for 5 minutes. You never generate or store one, and there's no variable for it.
Words on this page#
| Word | What it is |
|---|---|
| Team ID | Your developer account's 10-character ID |
| App ID | Your app's registration at Apple, named by its bundle ID such as com.example.app. Apple ties web sign-in to an App ID too, even if you only have a website |
| Services ID | The registration for signing in on websites, such as com.example.web. It says which domains and return URLs Apple may send people back to |
Key (.p8 file) |
A private key that proves requests come from your team. Apple lets you download it once |
| Key ID | The key's 10-character name |
| Return URL | The address on your API where Apple sends people back: Apple's name for a redirect URI |
How it works#
- Your website sends the browser to your API:
/v1/auth/apple/start. - Your API sends the browser on to Apple, with your Services ID.
- The person signs in at Apple. The first time, they choose whether to share their email address or hide it behind a relay address.
- Apple sends the browser back with a form POST to your API's return URL,
/v1/auth/apple/callback. - Your API checks Apple's answer with a client secret it signs with your key, signs the person in and sends them back to your website.
Your URLs#
| Field at Apple | Value (replace api.example.com with your API's host) |
|---|---|
| Domains and Subdomains | api.example.com (host only: no https://, no path) |
| Return URLs | https://api.example.com/v1/auth/apple/callback |
| Server-to-Server Notification Endpoint | https://api.example.com/v1/auth/apple/notifications |
Apple accepts only https addresses, and never localhost or IP addresses. For staging, register its host and return URL as well: a Services ID can hold several.
Step 1: Find your Team ID#
-
Open your developer account
Sign in at developer.apple.com/account with the Apple Account that belongs to your developer membership.
-
Copy the Team ID
Scroll to Membership details. Copy Team ID, 10 letters and digits:
terminalAPPLE_TEAM_ID=ABCDE12345
Step 2: Create or update the App ID#
-
Open Identifiers
In your account, open Certificates, IDs & Profiles → Identifiers.
-
If your app already has an App ID
Click it, tick Sign in with Apple under Capabilities, and click Save. Skip to step 5 below.
-
Otherwise, register one
Click + → App IDs → Continue → App → Continue. Description: your app's name. Bundle ID: Explicit, such as
com.example.app. If you have an iOS app, use its exact bundle ID from Xcode. -
Turn on Sign in with Apple
Under Capabilities, tick Sign in with Apple, leave it as a primary App ID, then Continue → Register.
-
Add the notification endpoint
Open the App ID again. Next to Sign in with Apple, click Edit (or Configure). In Server-to-Server Notification Endpoint, enter
https://api.example.com/v1/auth/apple/notificationsand save. Apple then tells your API when someone stops using Sign in with Apple with your app or deletes their Apple Account, and your API acts on it. -
If you have an iOS app
Put its bundle ID in
APPLE_BUNDLE_IDS, and in Xcode add the Sign in with Apple capability to your target (Signing & Capabilities → + Capability).
Step 3: Create the Services ID (websites)#
Skip this step if you only need sign-in inside iOS apps.
-
Register it
Identifiers → + → Services IDs → Continue. Description: shown to people on Apple's sign-in page, so use your app's name. Identifier: a reverse-domain name that differs from the App ID, such as
com.example.web. Click Continue → Register. -
Configure Sign in with Apple
Click the new Services ID, tick Sign in with Apple, and click Configure.
-
Fill in the website details
- Primary App ID: the App ID from step 2.
- Domains and Subdomains:
api.example.com. - Return URLs:
https://api.example.com/v1/auth/apple/callback.
Click Next → Done → Continue → Save.
-
Copy the identifier
terminalAPPLE_SERVICES_ID=com.example.web
Step 4: Create the key#
-
Start a key
Certificates, IDs & Profiles → Keys → +.
-
Name and enable it
Key Name: such as
acme Sign in with Apple. Tick Sign in with Apple → Configure → choose your primary App ID from step 2 → Save → Continue → Register. -
Download the file
Click Download. You get a file named like
AuthKey_XYZ987WVU6.p8. Apple lets you download it only once. If you lose it, revoke the key on the same page and create a new one. -
Copy the Key ID
It's on the page, and in the file name after
AuthKey_:terminalAPPLE_KEY_ID=XYZ987WVU6
The file is plain text. It should look like this, with different characters in the middle:
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg…
-----END PRIVATE KEY-----
Step 5: Store the key#
The .p8 file is the most sensitive value on this page: with it, anyone can act as your app at Apple. Keep it out of your repository.
On your computer, move it to a folder outside your app and point to it with a full path:
mkdir -p ~/.config/acme-api
mv ~/Downloads/AuthKey_XYZ987WVU6.p8 ~/.config/acme-api/
chmod 600 ~/.config/acme-api/AuthKey_XYZ987WVU6.p8
# in .env: a full path, not ~ and not a path inside the repository
APPLE_PRIVATE_KEY_FILE=/Users/you/.config/acme-api/AuthKey_XYZ987WVU6.p8
In production, use one of these:
- A secret file (preferred): upload the
.p8as a secret that your platform mounts as a file (Kubernetes secrets, Docker secrets, Fly.io or Render secret files), and setAPPLE_PRIVATE_KEY_FILEto where it's mounted, such as/run/secrets/apple_private_key. - A secret value: if your platform only has environment variables, set
APPLE_PRIVATE_KEYto the whole contents of the file, including theBEGINandENDlines and the line breaks.
Set one or the other, never both: the app refuses to start when both are set.
Step 6: Let Apple forward emails#
People can hide their email address. Apple then gives your app an address like abc123@privaterelay.appleid.com, and forwards email sent to it, but only from senders you've registered. Without this step, those people never receive their sign-up codes.
-
Open the email settings
Certificates, IDs & Profiles → Services → Sign in with Apple for Email Communication → Configure.
-
Add your sender
Click + and add the domain you send email from (the domain of
mail.from_email), or the exact sender address. -
Check your domain's records
Apple checks that the domain has an SPF record (and DKIM, ideally). Your email provider showed you these when you verified the domain (Email sending).
Step 7: Put the values in your app#
A website and an iOS app, in production:
APP_PUBLIC_URL=https://api.example.com
APPLE_TEAM_ID=ABCDE12345
APPLE_SERVICES_ID=com.example.web
APPLE_KEY_ID=XYZ987WVU6
APPLE_PRIVATE_KEY_FILE=/run/secrets/apple_private_key
APPLE_BUNDLE_IDS=com.example.app
Only an iOS app: leave APPLE_SERVICES_ID empty. APP_PUBLIC_URL is then only needed for other providers.
Restart the app after changing them.
Step 8: Try it#
Inside your iOS app#
This works on your computer without anything extra, because it doesn't use return URLs.
- Get a one-time value from
POST /v1/auth/apple/nonce. - With
ASAuthorizationAppleIDProvider, setrequest.nonceto the SHA-256 of that value, as lowercase hex. - Send
identityToken,authorizationCode, the original value, and the person's name (Apple only gives it the first time) toPOST /v1/auth/apple/tokenwith"transport": "bearer". - Your API answers with a session token, or with a second-factor challenge if the account has one. Store the token in the Keychain.
On a website, from your computer#
Apple needs a public https address, so give your computer one with a tunnel. For example, with Cloudflare's free tunnel tool (install cloudflared):
cloudflared tunnel --url http://localhost:8080
It prints an address such as https://random-words.trycloudflare.com. Then:
- Add that host to the Services ID's Domains and Subdomains, and
https://random-words.trycloudflare.com/v1/auth/apple/callbackto its Return URLs. - In
.env, setAPP_PUBLIC_URL=https://random-words.trycloudflare.comand restart. - In your browser, open
https://random-words.trycloudflare.com/v1/auth/apple/start, using the tunnel address throughout rather than localhost. - Sign in at Apple. You come back to
/docson the tunnel address, signed in;/v1/auth/meshows the account.
A quick tunnel's address changes each time you start it, so remove old ones from the Services ID when you're done. ngrok http 8080 works the same way.
Check it works#
go run ./cmd/api auth-providersshows✓ Apple sign-in(websites) and✓ Apple sign-in in iOS apps.GET /ops/auth/providersshows the same to administrators.- Signing in through
/v1/auth/apple/startends back on your site, signed in.
If something goes wrong#
| What you see | What it means | Fix |
|---|---|---|
The app won't start: sign-in with Apple also needs APPLE_KEY_ID, … |
Some Apple values are set and others are missing | Set the variables it lists, or empty every APPLE_ variable to turn Apple off |
The app won't start: APPLE_PRIVATE_KEY_FILE: … isn't a PEM private key |
The file isn't the .p8, or the value lost its BEGIN/END lines |
Point to the downloaded .p8; with APPLE_PRIVATE_KEY, paste the whole file including line breaks |
The app won't start: the Apple key must be a P-256 key |
A different kind of key, such as an APNs certificate | Create a key with Sign in with Apple ticked |
The app won't start: read APPLE_PRIVATE_KEY_FILE |
The path is wrong or not readable by the app | Use a full path, and check the file's permissions |
The app won't start: both variable and _FILE variant are set: APPLE_PRIVATE_KEY |
Both ways of giving the key are set | Keep one |
Apple shows invalid_request or Invalid redirect_uri |
The return URL isn't registered exactly, or isn't https | Add <APP_PUBLIC_URL>/v1/auth/apple/callback to the Services ID exactly |
Apple's sign-in fails with invalid_client |
The Services ID, Team ID or Key ID doesn't match, or the key was revoked or belongs to another App ID | Check all three values, and that the key is enabled for Sign in with Apple with the Services ID's primary App ID |
| People with hidden emails never get codes | Apple doesn't relay from your sender | Do step 6 |
| iOS: sign-in fails at your API | The app's bundle ID isn't in APPLE_BUNDLE_IDS, or the nonce wasn't hashed |
Add the bundle ID; send the original value, and give Apple its SHA-256 |