apple

von vercel

Emuliertes „Anmelden mit Apple“ / Apple OIDC für lokale Entwicklung und Tests. Verwenden, wenn der Benutzer die Apple-Anmeldung lokal testen oder Apple OIDC emulieren muss…

npx skills add https://github.com/vercel-labs/emulate --skill apple

Apple Sign In Emulator

Sign in with Apple emulation with authorization code flow, PKCE support, RS256 ID tokens, and OIDC discovery.

Start

# Apple only
npx emulate --service apple

# Default port (when run alone)
# http://localhost:4000

Or programmatically:

import { createEmulator } from 'emulate'

const apple = await createEmulator({ service: 'apple', port: 4004 })
// apple.url === 'http://localhost:4004'

Pointing Your App at the Emulator

Environment Variable

APPLE_EMULATOR_URL=http://localhost:4004

OAuth URL Mapping

Real Apple URLEmulator URL
https://appleid.apple.com/.well-known/openid-configuration$APPLE_EMULATOR_URL/.well-known/openid-configuration
https://appleid.apple.com/auth/authorize$APPLE_EMULATOR_URL/auth/authorize
https://appleid.apple.com/auth/token$APPLE_EMULATOR_URL/auth/token
https://appleid.apple.com/auth/keys$APPLE_EMULATOR_URL/auth/keys
https://appleid.apple.com/auth/revoke$APPLE_EMULATOR_URL/auth/revoke

Auth.js / NextAuth.js

import Apple from '@auth/core/providers/apple'

Apple({
  clientId: process.env.APPLE_CLIENT_ID,
  clientSecret: process.env.APPLE_CLIENT_SECRET,
  authorization: {
    url: `${process.env.APPLE_EMULATOR_URL}/auth/authorize`,
    params: { scope: 'openid email name', response_mode: 'form_post' },
  },
  token: {
    url: `${process.env.APPLE_EMULATOR_URL}/auth/token`,
  },
  jwks_endpoint: `${process.env.APPLE_EMULATOR_URL}/auth/keys`,
})

Passport.js

import { Strategy as AppleStrategy } from 'passport-apple'

const APPLE_URL = process.env.APPLE_EMULATOR_URL ?? 'https://appleid.apple.com'

new AppleStrategy({
  clientID: process.env.APPLE_CLIENT_ID,
  teamID: process.env.APPLE_TEAM_ID,
  keyID: process.env.APPLE_KEY_ID,
  callbackURL: 'http://localhost:3000/api/auth/callback/apple',
  authorizationURL: `${APPLE_URL}/auth/authorize`,
  tokenURL: `${APPLE_URL}/auth/token`,
}, verifyCallback)

Seed Config

apple:
  users:
    - email: testuser@icloud.com
      name: Test User
      given_name: Test
      family_name: User
    - email: private@example.com
      name: Private User
      is_private_email: true
  oauth_clients:
    - client_id: com.example.app
      team_id: TEAM001
      name: My Apple App
      redirect_uris:
        - http://localhost:3000/api/auth/callback/apple

When no OAuth clients are configured, the emulator accepts any client_id. With clients configured, strict validation is enforced for client_id and redirect_uri.

Users with is_private_email: true get a generated @privaterelay.appleid.com email in the id_token instead of their real email.

API Endpoints

OIDC Discovery

curl http://localhost:4004/.well-known/openid-configuration

Returns the standard OIDC discovery document with all endpoints pointing to the emulator:

{
  "issuer": "http://localhost:4004",
  "authorization_endpoint": "http://localhost:4004/auth/authorize",
  "token_endpoint": "http://localhost:4004/auth/token",
  "jwks_uri": "http://localhost:4004/auth/keys",
  "revocation_endpoint": "http://localhost:4004/auth/revoke",
  "response_types_supported": ["code"],
  "subject_types_supported": ["pairwise"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "scopes_supported": ["openid", "email", "name"],
  "token_endpoint_auth_methods_supported": ["client_secret_post"],
  "response_modes_supported": ["query", "fragment", "form_post"]
}

JWKS

curl http://localhost:4004/auth/keys

Returns an RSA public key (kid: emulate-apple-1) for verifying id_token signatures.

Authorization

# Browser flow: redirects to a user picker page
curl -v "http://localhost:4004/auth/authorize?\
client_id=com.example.app&\
redirect_uri=http://localhost:3000/api/auth/callback/apple&\
scope=openid+email+name&\
response_type=code&\
state=random-state&\
nonce=random-nonce&\
response_mode=form_post"

Query parameters:

ParamDescription
client_idOAuth client ID (Apple Services ID)
redirect_uriCallback URL
scopeSpace-separated scopes (openid email name)
stateOpaque state for CSRF protection
nonceNonce for ID token (optional)
response_modequery (default), form_post, or fragment

The emulator renders an HTML page where you select a seeded user. After selection, it redirects (or auto-submits a form for form_post) to redirect_uri with code and state. On the first authorization per user/client pair, a user JSON blob is also included (matching Apple's real behavior).

Token Exchange

curl -X POST http://localhost:4004/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "code=<authorization_code>&\
client_id=com.example.app&\
client_secret=<client_secret>&\
grant_type=authorization_code"

Returns:

{
  "access_token": "apple_...",
  "refresh_token": "r_apple_...",
  "id_token": "<jwt>",
  "token_type": "Bearer",
  "expires_in": 3600
}

The id_token is an RS256 JWT containing sub, email, email_verified (string), is_private_email (string), real_user_status, auth_time, and optional nonce.

Refresh Token

curl -X POST http://localhost:4004/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "refresh_token=r_apple_...&\
client_id=com.example.app&\
grant_type=refresh_token"

Returns a new access_token and id_token. No new refresh_token is issued on refresh (matching Apple's behavior).

Token Revocation

curl -X POST http://localhost:4004/auth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=apple_..."

Returns 200 OK. The token is removed from the emulator's token map.

Common Patterns

Full Authorization Code Flow

APPLE_URL="http://localhost:4004"
CLIENT_ID="com.example.app"
REDIRECT_URI="http://localhost:3000/api/auth/callback/apple"

# 1. Open in browser (user picks a seeded account)
#    $APPLE_URL/auth/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=openid+email+name&response_type=code&state=abc&response_mode=form_post

# 2. After user selection, emulator posts to:
#    $REDIRECT_URI with code=<code>&state=abc (and user JSON on first auth)

# 3. Exchange code for tokens
curl -X POST $APPLE_URL/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "code=<code>&client_id=$CLIENT_ID&grant_type=authorization_code"

# 4. Decode the id_token JWT to get user info

Private Relay Email

When a user has is_private_email: true in the seed config, the id_token will contain a generated @privaterelay.appleid.com email instead of the user's real email. This matches Apple's Hide My Email behavior.

Mehr Skills von vercel

benchmark-sandbox
vercel
Führt vercel-plugin-eval-Szenarien in Vercel Sandboxes aus, anstatt in lokalen WezTerm-Panels. Stellt temporäre Mikro-VMs mit vorinstalliertem Claude Code + Plugin bereit,…
official
emil-design-eng
vercel
Diese Fertigkeit kodiert Emil Kowalskis Philosophie zu UI-Verfeinerung, Komponentendesign, Animationsentscheidungen und den unsichtbaren Details, die Software großartig wirken lassen.
official
vercel-react-best-practices
vercel
Richtlinien zur Leistungsoptimierung von React und Next.js von Vercel Engineering. Diese Fähigkeit sollte beim Schreiben, Überprüfen oder Refaktorisieren von React/Next.js…
official
vercel-react-best-practices
vercel
Richtlinien zur Leistungsoptimierung von React und Next.js von Vercel Engineering. Diese Fähigkeit sollte beim Schreiben, Überprüfen oder Refaktorisieren von React/Next.js…
official
write-guide
vercel
Erstelle eine technische Anleitung, die einen realen Anwendungsfall durch progressive Beispiele vermittelt. Konzepte werden nur dann eingeführt, wenn der Leser sie benötigt.
official
release
vercel
Vercel-Plugin veröffentlichen — Gates ausführen, Version erhöhen, Artefakte generieren, committen und pushen. Verwenden, wenn aufgefordert, „zu veröffentlichen“, „auszuliefern“, „Version zu erhöhen und zu pushen“ oder „ein Release zu erstellen“.
official
deepsec
vercel
Führen Sie DeepSec gegen einen Vercel-Projekt-Checkout von dev3000 aus. Verwenden Sie für die Ein-Klick-DeepSec-Einrichtung, das Bootstrapping des Projektkontexts, die begrenzte Erstverarbeitung und…
official
backport-pr
vercel
Backportiere einen gemergten Next.js Pull-Request von canary auf einen früheren Release-Branch wie next-16-2. Verwende, wenn der Benutzer darum bittet, einen Backport, Cherry-Pick oder das Öffnen eines…
official