mcpField
A
apple

apple

github.com/vercel-labs测试与质量2.0k204 次安装6 天前更新
代码仓库

关于 apple

apple 是用于测试 Apple 登录集成的身份认证模拟器,面向接入 Sign in with Apple 的应用开发者。它提供授权码流程、PKCE、RS256 ID Token 和 OIDC 发现端点。

主要功能

  1. 01授权码认证流程
  2. 02支持 PKCE
  3. 03签发 RS256 ID Token
  4. 04提供 OIDC 发现与 JWKS
  5. 05模拟令牌刷新和撤销

适用场景

  1. 01测试 Apple 登录回调
  2. 02验证 PKCE 授权码交换
  3. 03测试 ID Token 签名校验
  4. 04检查私密转发邮箱行为

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/apple确认已加载。

详细介绍

Apple 登录模拟器

模拟使用 Apple 登录,支持授权码流程、PKCE、RS256 ID 令牌和 OIDC 发现。

启动

本地监听器默认绑定到 127.0.0.1。如需从容器或其他计算机访问,可向 CLI 传入 --host 0.0.0.0,或向 createEmulator 传入 hostname: '0.0.0.0'。配置 --base-url 或 baseUrl,以设置这些客户端可访问的对外公布 URL。

# Apple only
npx emulate --service apple

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

也可以通过编程方式使用:

import { createEmulator } from 'emulate'

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

将应用指向模拟器

环境变量
APPLE_EMULATOR_URL=http://localhost:4004
OAuth URL 映射
真实 Apple URL模拟器 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)

种子配置

apple:
  users:
    - email: [email protected]
      name: Test User
      given_name: Test
      family_name: User
    - email: [email protected]
      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_id。配置客户端后,将对 client_id 和 redirect_uri 进行严格验证。

对于 is_private_email: true 的用户,id_token 中会使用生成的 @privaterelay.appleid.com 邮箱,而非其真实邮箱。

API 端点

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

返回标准 OIDC 发现文档,其中所有端点都指向模拟器:

{
  "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

返回一个 RSA 公钥(kid:emulate-apple-1),用于验证 id_token 签名。

授权
# 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"

查询参数:

参数描述
client_idOAuth 客户端 ID(Apple Services ID)
redirect_uri回调 URL
scope以空格分隔的作用域(openid email name)
state用于防范 CSRF 的不透明状态值
nonceID 令牌的 nonce(可选)
response_modequery(默认)、form_post 或 fragment

模拟器会呈现一个 HTML 页面,供你选择已添加种子的用户。选择后,它会将用户重定向到 redirect_uri(若为 form_post,则自动提交表单),并附上 code 和 state。对于每个用户/客户端组合的首次授权,还会包含一个 user JSON 数据块(与 Apple 的实际行为一致)。

令牌交换
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"

返回:

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

id_token 是一个 RS256 JWT,包含 sub、email、email_verified(字符串)、is_private_email(字符串)、real_user_status、auth_time 和可选的 nonce。

刷新令牌
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"

返回新的 access_token 和 id_token。刷新时不会签发新的 refresh_token(与 Apple 的行为一致)。

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

返回 200 OK。该令牌会从模拟器的令牌映射中移除。

常见模式

完整授权码流程
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
私密转发邮箱

当种子配置中的用户设有 is_private_email: true 时,id_token 会包含生成的 @privaterelay.appleid.com 邮箱,而非用户的真实邮箱。这与 Apple 的“隐藏我的邮箱”行为一致。

与内置模拟器并用自定义模拟器

使用 npx emulate init --custom inventory 搭建第三方 API 模拟器并进行测试。使用 emulate 中的 defineConfig,将其注册到 emulate.config.ts 中,并与 { emulator: "apple" } 等内置条目并列。运行 npx emulate start --watch 可重新加载导入内容,并在打印出的 /_emulate URL 查看自定义状态。成功重新加载后,运行状态会重置为种子数据。现有的扁平种子配置仍然可用;--config 用于指定文件。有关编写和测试第三方 API 模拟器的信息,请参阅 https://emulate.dev/docs/custom-emulators。

评论

支持 Markdown