深色模式
接入Apple登录
概述
接入 Sign in with Apple,先区分登录入口:只有 Apple 原生 App、原生 App 加业务服务端、还是网页/Android 等非 Apple 平台。三种场景需要申请的东西不同;只做原生端时,不必为了“以后可能要服务端”提前创建 Services ID 和 .p8 私钥。
下面的清单以 Apple Developer 后台当前配置为准。正式项目应由开发团队的 Account Holder 或 Admin 操作;Apple Account 本身需要开启双重认证。
申请清单
| 场景 | 必须申请或配置 | 通常不需要 |
|---|---|---|
| 仅 iOS、macOS、tvOS、watchOS 原生登录 | 显式 App ID、Sign in with Apple capability、签名配置 | Services ID、私钥、网页域名、回调地址 |
| 原生 App + 自有服务端 | 上述项目;Sign in with Apple 私钥、Key ID、Team ID;服务端 token 验证 | Services ID(没有网页登录时) |
| 网页或 Android 登录 | 一个已启用且作为 primary 的 Apple 平台 App ID;Services ID;域名与回调地址;私钥、Key ID、Team ID | 原生 App 目标的 capability(纯网页入口不使用它) |
| 要向隐藏邮箱发信 | 上述项目;Private Email Relay 的发信域名、子域名或发件地址;SPF/DKIM | 把隐藏邮箱当作普通邮箱直接投递 |
申请入口在 Apple Developer 的 Certificates, Identifiers & Profiles。没有可用团队时,先注册 Apple Developer;需要签名、分发或完整生产配置时,再完成 Apple Developer Program 的团队开通。权限不足时不要共用 Account Holder 的账号,邀请到团队并授予对应角色即可。
App ID配置
先创建或确认一个显式 App ID,其 Bundle ID 必须与 Xcode target 的 Bundle Identifier 完全一致。通配符 App ID 不适合承载这类 capability。
在 Identifiers 中打开该 App ID,启用 Sign in with Apple:
- 新产品或第一个关联产品选 Enable as a primary App ID。
- 已有同一账号体系的 iOS、macOS 等产品时,相关 App ID 选择加入既有 primary App ID 的 group。
- 有服务端时,可在 primary App ID 上填写一条 server-to-server notification URL;必须是包含协议、主机和路径的绝对
HTTPS地址。
应用分组会影响用户标识与首次授权体验。相关 App 和网站应在一开始就归入同一 primary App ID;上线后再改分组会增加账号合并和迁移成本。关闭 capability 会重置已保存的配置,不能把它当作排错开关。
Xcode 中还要在 target 的 Signing & Capabilities 添加 Sign in with Apple。使用自动签名时,Xcode 会同步为 App ID 开启 capability;手工签名时,更新 App ID 后应重新生成受影响的 provisioning profile。
网页配置
网页、Android 或其他不能直接使用 AuthenticationServices 的平台,需要额外创建 Services ID:
- 在
Identifiers点击+,创建Services ID,记录它的 identifier;它就是网页 OAuth 的client_id。 - 打开该 Services ID 的 Sign in with Apple 配置,关联前面选定的 primary App ID。
- 填写实际使用的域名、子域名和 return URL,例如
https://auth.example.com/apple/callback。 - 保存后,前端使用 Sign in with Apple JS 或标准授权跳转;回调由服务端接收并校验。
return URL 必须是含 scheme、host、path 的绝对地址;网页配置不能使用 IP 地址、localhost 或带 #fragment 的地址。开发环境可用一个可被公网访问的 HTTPS 测试域名,不要把生产回调改成临时内网地址。
个人团队每个 Services ID 最多登记 10 个 website URL,组织团队最多 100 个。预发布、正式站点的域名和回调地址应在上线前一次登记完整。
私钥与密钥
只有服务端需要用授权码换 token、刷新 token 或撤销授权时,才创建 Sign in with Apple key。创建后下载 .p8,同时记录:
| 配置 | 用途 | 保存位置 |
|---|---|---|
TEAM_ID | client secret 的 iss | 服务端密钥配置 |
KEY_ID | JWT header 的 kid | 服务端密钥配置 |
.p8 私钥 | 使用 ES256 签发 client secret | KMS、密钥管理服务或受限 Secret |
CLIENT_ID | 原生为 Bundle ID;网页为 Services ID | 服务端配置 |
每个 primary App ID 最多可关联两把此类私钥。.p8 只能下载一次,不提交 Git、不打进 App、不放前端环境变量,也不要粘贴到工单。轮换时先创建新密钥、切换服务端、确认生效后再撤销旧密钥,避免整个登录入口一起下线。
服务端的 client_secret 是开发方签发的 JWT,使用 ES256 和 .p8 签名。payload 至少包括:iss = TEAM_ID、sub = CLIENT_ID、aud = https://appleid.apple.com、iat、exp;exp 距 Apple 服务器时间最长六个月。用它向 https://appleid.apple.com/auth/token 提交 authorization_code 或 refresh_token 时,还需要携带对应的 client_id。
原生登录
原生 App 使用 AuthenticationServices,按钮优先使用系统的 SignInWithAppleButton 或 ASAuthorizationAppleIDButton,不要自行拼 Apple 标志和文案。授权请求中可按业务需要请求 .fullName、.email,并传入随机 nonce 与登录事务关联。
swift
let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = nonce
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()成功回调里的 credential.user 是业务账户绑定的主键,应保存并以它识别用户,不要以邮箱作为主键。fullName 和用户选择的 email 只会在首次授权时可靠地回传,收到后立即写入服务端;后续登录回调出现 nil 是正常行为。
有业务服务端时,App 将 identityToken、单次使用的 authorizationCode、user、原始 nonce 一起传给服务端。服务端验证 token 签名和 claim 后创建或登录本地账户,再由自己的 session 或 access token 维持登录态。客户端上传的昵称、邮箱、用户 ID 不能直接作为可信身份。
服务端校验
服务端最少应执行以下检查:
- 从 Apple 的 JWK 集获取公钥并验证
id_token的签名与alg。 - 校验
iss是https://appleid.apple.com,aud等于当前 Bundle ID 或 Services ID,且exp、nonce、授权事务均有效。 - 以
sub/原生user绑定本地账户,不按 email 建唯一用户;同一用户可选择隐藏真实邮箱。 - 立即且幂等地使用
authorizationCode换 token;它是一次性凭据。refresh token 和.p8一样按高敏感密钥保存。 - 网页流程校验发起时保存的
state,防止 CSRF;原生和网页都应限制 nonce 重放。
Apple 的用户标识在同一开发团队内唯一且稳定,但跨开发团队不同。App 转让、团队迁移或拆分账号体系前,需要先按 Apple 的用户迁移流程处理,不能直接把 sub 当作跨团队永久不变的 ID。
隐藏邮箱
用户请求 email scope 后,可以选择 Hide My Email。此时收到的是转发地址;它不等同于可用于密码找回或账号合并的真实邮箱。Apple 已公告:2026 年晚些时候新发放的地址将从 @privaterelay.appleid.com 迁移到 @private.icloud.com,旧地址继续可用。因此邮箱校验、白名单和正则应同时接受两种域名。
只要产品会给该地址发送验证码、订单或通知邮件,就在 Private Email Relay 中登记发件域名、子域名或具体发件地址,并配置 SPF,尽量同时配置 DKIM。未登记来源的邮件可能被 relay 拒收;每个私有转发地址每天的收发总量也有限制,不应把它当作营销群发通道。
上线检查
- App ID 已启用 capability,Xcode target 的 entitlement 和签名 profile 已更新。
- primary App ID、Services ID、相关 App ID 的分组关系已确认,生产后不随意更改。
- 网页的域名与每一个 callback URL 都已登记,回调支持 HTTPS。
Team ID、Key ID、CLIENT_ID、.p8已配置到生产 Secret;私钥从未进入仓库和客户端包。- 服务端已校验签名、
iss、aud、exp、nonce和网页state,并对授权码及账号创建做幂等处理。 - 首次登录就持久化
sub、姓名与邮箱;账户合并有显式流程,不能仅靠邮箱猜测。 - 需要邮件时,Private Email Relay 发信源已验证,系统接受
@privaterelay.appleid.com和@private.icloud.com。 - 已配置并验证 server-to-server notification;收到
email-disabled、consent-revoked、account-deleted时会更新账号和发信状态。
