apple
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseApple Sign In Emulator
Apple登录模拟器
Sign in with Apple emulation with authorization code flow, PKCE support, RS256 ID tokens, and OIDC discovery.
支持授权码流程、PKCE、RS256 ID令牌和OIDC发现的Apple登录模拟工具。
Start
启动
bash
undefinedbash
undefinedApple only
Apple only
npx emulate --service apple
npx emulate --service apple
Default port (when run alone)
Default port (when run alone)
Or programmatically:
```typescript
import { createEmulator } from 'emulate'
const apple = await createEmulator({ service: 'apple', port: 4004 })
// apple.url === 'http://localhost:4004'
或者通过编程方式使用:
```typescript
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
环境变量
bash
APPLE_EMULATOR_URL=http://localhost:4004bash
APPLE_EMULATOR_URL=http://localhost:4004OAuth URL Mapping
OAuth URL映射
| Real Apple URL | Emulator URL |
|---|---|
| |
| |
| |
| |
| |
| 真实Apple URL | 模拟器URL |
|---|---|
| |
| |
| |
| |
| |
Auth.js / NextAuth.js
Auth.js / NextAuth.js
typescript
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`,
})typescript
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
Passport.js
typescript
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)typescript
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
种子配置
yaml
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/appleWhen no OAuth clients are configured, the emulator accepts any . With clients configured, strict validation is enforced for and .
client_idclient_idredirect_uriUsers with get a generated email in the instead of their real email.
is_private_email: true@privaterelay.appleid.comid_tokenyaml
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当未配置OAuth客户端时,模拟器接受任意。配置客户端后,会对和进行严格验证。
client_idclient_idredirect_uri设置的用户,其中会使用生成的邮箱,而非真实邮箱。
is_private_email: trueid_token@privaterelay.appleid.comAPI Endpoints
API端点
OIDC Discovery
OIDC发现
bash
curl http://localhost:4004/.well-known/openid-configurationReturns the standard OIDC discovery document with all endpoints pointing to the emulator:
json
{
"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"]
}bash
curl http://localhost:4004/.well-known/openid-configuration返回标准OIDC发现文档,所有端点均指向模拟器:
json
{
"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
JWKS
bash
curl http://localhost:4004/auth/keysReturns an RSA public key (: ) for verifying signatures.
kidemulate-apple-1id_tokenbash
curl http://localhost:4004/auth/keys返回用于验证签名的RSA公钥(: )。
id_tokenkidemulate-apple-1Authorization
授权
bash
undefinedbash
undefinedBrowser flow: redirects to a user picker page
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"
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:
| Param | Description |
|-------|-------------|
| `client_id` | OAuth client ID (Apple Services ID) |
| `redirect_uri` | Callback URL |
| `scope` | Space-separated scopes (`openid email name`) |
| `state` | Opaque state for CSRF protection |
| `nonce` | Nonce for ID token (optional) |
| `response_mode` | `query` (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).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"
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"
查询参数:
| 参数 | 描述 |
|-------|-------------|
| `client_id` | OAuth客户端ID(Apple服务ID) |
| `redirect_uri` | 回调URL |
| `scope` | 空格分隔的权限范围(`openid email name`) |
| `state` | 用于CSRF保护的随机状态值 |
| `nonce` | ID令牌的随机值(可选) |
| `response_mode` | `query`(默认)、`form_post`或`fragment` |
模拟器会渲染一个HTML页面供你选择预配置的用户。选择后,它会重定向(或针对`form_post`自动提交表单)到`redirect_uri`,并携带`code`和`state`。在**首次**对用户/客户端对进行授权时,还会包含一个`user` JSON对象(与Apple的实际行为一致)。Token Exchange
令牌交换
bash
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:
json
{
"access_token": "apple_...",
"refresh_token": "r_apple_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600
}The is an RS256 JWT containing , , (string), (string), , , and optional .
id_tokensubemailemail_verifiedis_private_emailreal_user_statusauth_timenoncebash
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"返回结果:
json
{
"access_token": "apple_...",
"refresh_token": "r_apple_...",
"id_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600
}id_tokensubemailemail_verifiedis_private_emailreal_user_statusauth_timenonceRefresh Token
刷新令牌
bash
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 and . No new is issued on refresh (matching Apple's behavior).
access_tokenid_tokenrefresh_tokenbash
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"返回新的和。刷新时不会颁发新的(与Apple的实际行为一致)。
access_tokenid_tokenrefresh_tokenToken Revocation
令牌撤销
bash
curl -X POST http://localhost:4004/auth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=apple_..."Returns . The token is removed from the emulator's token map.
200 OKbash
curl -X POST http://localhost:4004/auth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=apple_..."返回。该令牌会从模拟器的令牌映射中移除。
200 OKCommon Patterns
常见模式
Full Authorization Code Flow
完整授权码流程
bash
APPLE_URL="http://localhost:4004"
CLIENT_ID="com.example.app"
REDIRECT_URI="http://localhost:3000/api/auth/callback/apple"bash
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)
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
$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:
2. After user selection, emulator posts to:
$REDIRECT_URI with code=<code>&state=abc (and user JSON on first auth)
$REDIRECT_URI with code=<code>&state=abc (and user JSON on first auth)
3. Exchange code for tokens
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"
-H "Content-Type: application/x-www-form-urlencoded"
-d "code=<code>&client_id=$CLIENT_ID&grant_type=authorization_code"
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"
-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
4. Decode the id_token JWT to get user info
undefinedundefinedPrivate Relay Email
隐私中继邮箱
When a user has in the seed config, the will contain a generated email instead of the user's real email. This matches Apple's Hide My Email behavior.
is_private_email: trueid_token@privaterelay.appleid.com当种子配置中的用户设置时,将包含生成的邮箱,而非用户的真实邮箱。这与Apple的“隐藏我的邮箱”行为一致。
is_private_email: trueid_token@privaterelay.appleid.com