Apple

Apple 提供者的设置与使用。

获取 OAuth 凭证

使用 Apple 登录时,您需要一个客户端 ID、团队 ID、密钥 ID 和私钥。您可以从 Apple 开发者门户 获取它们,然后用它们生成客户端密钥 JWT。

您需要一个有效的 Apple 开发者账户 才能访问开发者门户并生成这些凭证。

按照以下步骤设置您的 App ID、服务 ID 并生成客户端密钥所需的密钥:

  1. 导航到证书、标识符与配置文件: 在 Apple 开发者门户中,转到“证书、标识符与配置文件”部分。

  2. 创建 App ID:

    • 进入“标识符”标签页。
    • 点击标识符旁边的 + 图标。
    • 选择 App IDs,然后点击 继续。
    • 选择 App 作为类型,然后点击 继续。
    • 描述: 输入您的应用名称(例如,“我的出色应用”)。此名称在用户登录时可能会显示给用户。
    • 捆绑标识符: 设置一个捆绑标识符。推荐的格式是反向域名(例如,com.yourcompany.yourapp)。使用 .ai(用于应用标识符)等后缀有助于组织,但不是必需的(例如,com.yourcompany.yourapp.ai)。
    • 向下滚动到 功能。选择 使用 Apple 登录 的复选框。
    • 点击 继续,然后点击 注册。
  3. 创建服务 ID:

    • 返回“标识符”标签页。
    • 点击 + 图标。
    • 选择 服务 ID,然后点击 继续。
    • 描述: 输入此服务的描述(例如,再次输入您的应用名称)。
    • 标识符: 为服务设置一个唯一标识符。将其设置为与您的 App ID 不同的反向域名格式(例如,com.yourcompany.yourapp.si,其中 .si 表示服务标识符——这仅用于您的组织,并非必需)。此服务 ID 将作为您的 clientId。
    • 点击 继续,然后点击 注册。
  4. 配置服务 ID:

    • 在 标识符 列表中找到您刚刚创建的服务 ID 并点击它。
    • 勾选 使用 Apple 登录 的功能,然后点击 配置。
    • 在 主 App ID 下,选择您之前创建的 App ID(例如,com.yourcompany.yourapp.ai)。
    • 在 域和子域 下,列出您将用于 Apple 登录的所有根域(例如,example.com、anotherdomain.com)。
    • 在 返回 URL 中,输入回调 URL:https://yourdomain.com/api/auth/callback/apple。添加所有必要的返回 URL。
    • 点击 下一步,然后点击 完成。
    • 点击 继续,然后点击 保存。
  5. 创建客户端密钥:

    • 进入 密钥 标签页。
    • 点击 + 图标以创建新密钥。
    • 密钥名称: 输入密钥名称(例如,“使用 Apple 登录密钥”)。
    • 向下滚动并选择 使用 Apple 登录 的复选框。
    • 点击 使用 Apple 登录 旁边的 配置 按钮。
    • 选择您之前创建的 主 App ID。
    • 点击 保存,然后点击 继续,再点击 注册。
    • 下载密钥: 立即下载 .p8 密钥文件。此文件只能下载一次。 记下密钥 ID(在创建密钥后可在密钥页面找到)和您的团队 ID(可在 Apple 开发者账户设置中找到)。
  6. 生成客户端密钥(JWT): Apple 要求使用下载的 .p8 密钥、密钥 ID 和团队 ID 动态生成 JSON Web 令牌(JWT)。此 JWT 作为您的 clientSecret。

    我们建议您在身份验证配置中使用下方的 jose 示例 生成客户端密钥 JWT。您也可以按照 Apple 的文档 手动生成,然后将生成的 JWT 作为 clientSecret 传入。

    注意: Apple 会拒绝未来过期时间超过 15,777,000 秒(六个月)的客户端密钥 JWT。为了保持身份验证不中断,您需要在过期前重新生成客户端密钥。

Apple 的 OAuth 实现要求 clientSecret 使用 JWT(JSON Web 令牌),而不是共享密钥字符串。

根据 Apple 文档,这些 JWT:

  • 未来过期时间不能超过 15,777,000 秒(六个月)
  • 必须使用您的私钥进行加密签名

安装 jose 包以生成客户端密钥 JWT。

npm install jose

配置提供者

要配置提供者,您需要将其添加到身份验证实例的 socialProviders 选项中。

您还需要在身份验证实例配置中将 https://appleid.apple.com 添加到 trustedOrigins 数组中,以允许与 Apple 的身份验证服务器通信。

auth.ts
import { betterAuth } from "better-auth"
import { importPKCS8, SignJWT } from "jose";

// 生成 '使用 Apple 登录' 所需的客户端密钥 JWT。
async function generateAppleClientSecret(clientId, teamId, keyId, privateKey) {
    const key = await importPKCS8(privateKey, "ES256");
    const now = Math.floor(Date.now() / 1000);
    return new SignJWT({})
        .setProtectedHeader({ alg: "ES256", kid: keyId })
        .setIssuer(teamId)
        .setSubject(clientId)
        .setAudience("https://appleid.apple.com")
        .setIssuedAt(now)
        .setExpirationTime(now + 180 * 24 * 60 * 60)
        .sign(key);
}

export const auth = betterAuth({
    socialProviders: {
        apple: async () => ({ 
            clientId: process.env.APPLE_CLIENT_ID as string, 
            clientSecret: await generateAppleClientSecret(
                process.env.APPLE_CLIENT_ID!, 
                process.env.APPLE_TEAM_ID!, 
                process.env.APPLE_KEY_ID!, 
                process.env.APPLE_PRIVATE_KEY!, 
            ), 
            // 可选
            appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER as string, 
        }), 
    },
    // 为 Apple 登录流程添加 appleid.apple.com 到受信任来源
    trustedOrigins: ["https://appleid.apple.com"], 
})

在原生 iOS 上,它使用的不是服务 ID,而是应用 ID(bundle ID)作为客户端 ID,因此如果在 signIn.social 中将服务 ID 作为 clientId 并使用 idToken,则会抛出错误:JWTClaimValidationFailed: unexpected "aud" claim value。因此,当您想使用 ID Token 通过 Apple 登录时,需要提供 appBundleIdentifier。

对于多个静态受众(例如服务 ID 加原生 bundle ID),请优先使用 clientId: string[] 或 audience: string[],而不是仅设置 appBundleIdentifier。当受众必须依赖请求时(例如读取 x-platform 头),请使用自定义的 verifyIdToken 回调——它会将请求上下文作为第三个参数传入。

Apple 仅在首次授权时返回 email 声明;此后的每次登录都会省略它,而且 Apple 不提供可稍后获取它的用户信息端点。有关持久化的指导以及 mapProfileToUser 回退,请参见 Handling Providers Without Email。

本地主机和非 TLS 限制

Apple 登录不支持 localhost 或非 HTTPS URL。开发期间:

  • 您不能使用 http://localhost 作为返回 URL
  • 您必须使用具有有效 HTTPS/TLS 证书的域

此限制由 Apple 的安全要求强制执行,无法绕过。

使用方法

Apple 登录

要使用 Apple 登录,您可以使用客户端提供的 signIn.social 函数。signIn 函数接收一个包含以下属性的对象:

  • provider:要使用的提供者。应设置为 apple。
auth-client.ts
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "apple"
    })
}

使用 ID 令牌进行 Apple 登录

要使用 ID 令牌进行 Apple 登录,您可以使用 signIn.social 函数传递 ID 令牌。

当您在客户端拥有 Apple 的 ID 令牌并希望在服务器端使用它进行登录时,这非常有用。

如果提供了 ID 令牌,则不会发生重定向,用户将直接登录。

auth-client.ts
await authClient.signIn.social({
    provider: "apple",
    idToken: {
        token: // Apple ID 令牌,
        nonce: // 随机数(可选)
        accessToken: // 访问令牌(可选)
    }
})

生成 Apple 客户端密钥(JWT)

如果您之前使用过本节中的页面内生成器,请改用上面的 jose 示例。它使用 Better Auth 的异步提供者配置,通过您的客户端 ID、团队 ID、密钥 ID 和私钥生成 Apple 客户端密钥 JWT。

该示例使用 180 天的过期时间,低于 Apple 的六个月限制。通过配置生成令牌,可以避免将静态生成的令牌复制到您的身份验证设置中。

或者,您仍然可以按照 Apple 的文档 手动生成客户端密钥 JWT,然后将生成的 JWT 作为 clientSecret 传入。如果您选择这种方式,请记得在它过期前替换它。